From 4b62d73d47b71f3113b91ab228dad7cfc3a317d0 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 29 Apr 2026 15:00:32 -0700 Subject: [PATCH 01/24] Migrate Apple reachability from SCNetworkReachability to NWPathMonitor Replace the deprecated SCNetworkReachability APIs with NWPathMonitor for modern Apple deployment targets (iOS 12+, macOS 10.14+). The legacy SCNetworkReachability path is retained behind a compile-time check for older targets. Changes: - NetworkInformationImpl.mm: refactor to use NWPathMonitor as the primary reachability mechanism, with SCNetworkReachability as fallback for older deployment targets only - ODWReachability.h/m: add NWPathMonitor-based implementation gated on availability, keeping SCNetworkReachability for backward compatibility - Remove dead private header imports from tests This eliminates the -Wdeprecated-declarations build failures on Xcode 26.4+ without needing pragma suppressions. Fixes #1425 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/pal/posix/NetworkInformationImpl.mm | 220 ++++++++++-------- tests/unittests/obj-c/ODWReachabilityTests.mm | 2 - third_party/Reachability/ODWReachability.h | 12 + third_party/Reachability/ODWReachability.m | 57 ++++- 4 files changed, 188 insertions(+), 103 deletions(-) diff --git a/lib/pal/posix/NetworkInformationImpl.mm b/lib/pal/posix/NetworkInformationImpl.mm index 24c7839eb..943c22667 100644 --- a/lib/pal/posix/NetworkInformationImpl.mm +++ b/lib/pal/posix/NetworkInformationImpl.mm @@ -20,7 +20,7 @@ class NetworkInformation : public NetworkInformationImpl, public std::enable_shared_from_this { - public: + public: /// /// /// @@ -59,21 +59,27 @@ virtual NetworkCost GetNetworkCost() /// /// Setup initial network information and start net monitor if requested. /// This cannot be put in constructor because we need to use shared_from_this. - /// - void SetupNetDetect(); + /// + void SetupNetDetect(); - private: - void UpdateType(NetworkType type) noexcept; - void UpdateCost(NetworkCost cost) noexcept; - std::string m_network_provider {}; + private: + void SetupModernNetDetect() API_AVAILABLE(macos(10.14), ios(12.0)); +#if ODW_LEGACY_REACHABILITY_REQUIRED + void SetupLegacyNetDetect(); +#endif + void UpdateType(NetworkType type) noexcept; + void UpdateCost(NetworkCost cost) noexcept; + std::string m_network_provider {}; - // iOS 12 and newer - nw_path_monitor_t m_monitor = nil; + // iOS 12+ / macOS 10.14+ + nw_path_monitor_t m_monitor = nil; - // iOS 11 and older - ODWReachability* m_reach = nil; - id m_notificationId = nil; - }; +#if ODW_LEGACY_REACHABILITY_REQUIRED + // Older Apple deployment targets still need the legacy fallback. + ODWReachability* m_reach = nil; + id m_notificationId = nil; +#endif + }; NetworkInformation::NetworkInformation(IRuntimeConfig& configuration) : NetworkInformationImpl(configuration) @@ -84,6 +90,7 @@ virtual NetworkCost GetNetworkCost() NetworkInformation::~NetworkInformation() noexcept { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { if (m_isNetDetectEnabled) @@ -99,108 +106,130 @@ virtual NetworkCost GetNetworkCost() [m_reach stopNotifier]; } } +#else + if (m_isNetDetectEnabled) + { + nw_path_monitor_cancel(m_monitor); + } +#endif } - void NetworkInformation::SetupNetDetect() + void NetworkInformation::SetupModernNetDetect() { - if (@available(macOS 10.14, iOS 12.0, *)) + auto weak_this = std::weak_ptr(shared_from_this()); + + m_monitor = nw_path_monitor_create(); + nw_path_monitor_set_queue(m_monitor, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)); + nw_path_monitor_set_update_handler(m_monitor, ^(nw_path_t path) { - auto weak_this = std::weak_ptr(shared_from_this()); + auto strong_this = weak_this.lock(); + if (!strong_this) + { + return; + } - m_monitor = nw_path_monitor_create(); - nw_path_monitor_set_queue(m_monitor, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)); - nw_path_monitor_set_update_handler(m_monitor, ^(nw_path_t path) + NetworkType type = NetworkType_Unknown; + NetworkCost cost = NetworkCost_Unknown; + nw_path_status_t status = nw_path_get_status(path); + bool connected = status == nw_path_status_satisfied || status == nw_path_status_satisfiable; + if (connected) { - auto strong_this = weak_this.lock(); - if (!strong_this) + if (nw_path_uses_interface_type(path, nw_interface_type_wifi)) { - return; + type = NetworkType_Wifi; } - - NetworkType type = NetworkType_Unknown; - NetworkCost cost = NetworkCost_Unknown; - nw_path_status_t status = nw_path_get_status(path); - bool connected = status == nw_path_status_satisfied || status == nw_path_status_satisfiable; - if (connected) + else if (nw_path_uses_interface_type(path, nw_interface_type_cellular)) { - if (nw_path_uses_interface_type(path, nw_interface_type_wifi)) - { - type = NetworkType_Wifi; - } - else if (nw_path_uses_interface_type(path, nw_interface_type_cellular)) - { - type = NetworkType_WWAN; - } - else if (nw_path_uses_interface_type(path, nw_interface_type_wired)) - { - type = NetworkType_Wired; - } - cost = nw_path_is_expensive(path) ? NetworkCost_Metered : NetworkCost_Unmetered; - if (@available(macOS 10.15, iOS 13.0, *)) + type = NetworkType_WWAN; + } + else if (nw_path_uses_interface_type(path, nw_interface_type_wired)) + { + type = NetworkType_Wired; + } + cost = nw_path_is_expensive(path) ? NetworkCost_Metered : NetworkCost_Unmetered; + if (@available(macOS 10.15, iOS 13.0, *)) + { + if (nw_path_is_constrained(path)) { - if (nw_path_is_constrained(path)) - { - cost = NetworkCost_Roaming; - } + cost = NetworkCost_Roaming; } } - strong_this->UpdateType(type); - strong_this->UpdateCost(cost); - }); - nw_path_monitor_start(m_monitor); - - // nw_path_monitor_start will invoke the callback for once. So if - // we don't want to listen for changes, we can just start the - // monitor and stop it right away. - if (!m_isNetDetectEnabled) - { - nw_path_monitor_cancel(m_monitor); } - } - else + strong_this->UpdateType(type); + strong_this->UpdateCost(cost); + }); + nw_path_monitor_start(m_monitor); + + // nw_path_monitor_start will invoke the callback for once. So if + // we don't want to listen for changes, we can just start the + // monitor and stop it right away. + if (!m_isNetDetectEnabled) { - auto weak_this = std::weak_ptr(shared_from_this()); + nw_path_monitor_cancel(m_monitor); + } + } - m_reach = [ODWReachability reachabilityForInternetConnection]; - void (^block)(NSNotification*) = ^(NSNotification*) - { - auto strong_this = weak_this.lock(); - if (!strong_this) - { - return; - } +#if ODW_LEGACY_REACHABILITY_REQUIRED + void NetworkInformation::SetupLegacyNetDetect() + { + auto weak_this = std::weak_ptr(shared_from_this()); - // NetworkCost information is not available until iOS 12. - // Just make the best guess here. - switch (m_reach.currentReachabilityStatus) - { - case NotReachable: - strong_this->UpdateType(NetworkType_Unknown); - strong_this->UpdateCost(NetworkCost_Unknown); - break; - case ReachableViaWiFi: - strong_this->UpdateType(NetworkType_Wifi); - strong_this->UpdateCost(NetworkCost_Unmetered); - break; - case ReachableViaWWAN: - strong_this->UpdateType(NetworkType_WWAN); - strong_this->UpdateCost(NetworkCost_Metered); - break; - } - }; - block(nil); // Update the initial status. + m_reach = [ODWReachability reachabilityForInternetConnection]; + void (^block)(NSNotification*) = ^(NSNotification*) + { + auto strong_this = weak_this.lock(); + if (!strong_this) + { + return; + } - if (m_isNetDetectEnabled) + // NetworkCost information is not available until iOS 12. + // Just make the best guess here. + switch (m_reach.currentReachabilityStatus) { - m_notificationId = - [[NSNotificationCenter defaultCenter] - addObserverForName: kNetworkReachabilityChangedNotification - object: nil - queue: nil - usingBlock: block]; - [m_reach startNotifier]; + case NotReachable: + strong_this->UpdateType(NetworkType_Unknown); + strong_this->UpdateCost(NetworkCost_Unknown); + break; + case ReachableViaWiFi: + strong_this->UpdateType(NetworkType_Wifi); + strong_this->UpdateCost(NetworkCost_Unmetered); + break; + case ReachableViaWWAN: + strong_this->UpdateType(NetworkType_WWAN); + strong_this->UpdateCost(NetworkCost_Metered); + break; } + }; + block(nil); // Update the initial status. + + if (m_isNetDetectEnabled) + { + m_notificationId = + [[NSNotificationCenter defaultCenter] + addObserverForName: kNetworkReachabilityChangedNotification + object: nil + queue: nil + usingBlock: block]; + [m_reach startNotifier]; + } + } +#endif + + void NetworkInformation::SetupNetDetect() + { +#if ODW_LEGACY_REACHABILITY_REQUIRED + if (@available(macOS 10.14, iOS 12.0, *)) + { + SetupModernNetDetect(); + } + else + { + SetupLegacyNetDetect(); } +#else + SetupModernNetDetect(); +#endif } void NetworkInformation::UpdateType(NetworkType type) noexcept @@ -229,4 +258,3 @@ virtual NetworkCost GetNetworkCost() } } PAL_NS_END - diff --git a/tests/unittests/obj-c/ODWReachabilityTests.mm b/tests/unittests/obj-c/ODWReachabilityTests.mm index cc8a7c24e..d231bbec8 100644 --- a/tests/unittests/obj-c/ODWReachabilityTests.mm +++ b/tests/unittests/obj-c/ODWReachabilityTests.mm @@ -11,8 +11,6 @@ #import #import "ODWReachability.h" -#import -#import #import @interface ODWReachabilityTests : XCTestCase diff --git a/third_party/Reachability/ODWReachability.h b/third_party/Reachability/ODWReachability.h index b2d218417..57f3702f9 100644 --- a/third_party/Reachability/ODWReachability.h +++ b/third_party/Reachability/ODWReachability.h @@ -25,8 +25,10 @@ POSSIBILITY OF SUCH DAMAGE. */ +#import #import #import +#import /** @@ -40,6 +42,16 @@ extern NSString* const kNetworkReachabilityChangedNotification; +// Older Apple deployment targets still need the legacy SCNetworkReachability +// backend at runtime. Newer targets can compile directly to the modern path. +#if TARGET_OS_IPHONE +#define ODW_LEGACY_REACHABILITY_REQUIRED (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0) +#elif TARGET_OS_OSX +#define ODW_LEGACY_REACHABILITY_REQUIRED (__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_14) +#else +#define ODW_LEGACY_REACHABILITY_REQUIRED 0 +#endif + typedef NS_ENUM(NSInteger, ODWNetworkStatus) { // Apple NetworkStatus Compatible Names. NotReachable = 0, diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 7947f7df0..dc8deb38f 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -27,8 +27,6 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF #import "ODWReachability.h" -#import -#import #import @@ -43,6 +41,8 @@ @interface ODWReachability () -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; ++(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:(NSError *)error url:(NSURL *)url; +-(SCNetworkReachabilityFlags)checkNetworkReachability:(BOOL)checkData; @end @@ -65,6 +65,7 @@ -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; } +#if ODW_LEGACY_REACHABILITY_REQUIRED // Start listening for reachability notifications on the current run loop static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) { @@ -79,6 +80,7 @@ static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRea [reachability reachabilityChanged:flags]; } } +#endif @implementation ODWReachability @@ -99,8 +101,10 @@ +(ODWReachability*)reachabilityWithHostName:(NSString*)hostname +(instancetype)reachabilityWithHostname:(NSString*)hostname { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif // Use URLSession for macOS 10.14 or higher NSString *formattedHostname = hostname; if (![formattedHostname hasPrefix:@"https://"] && ![formattedHostname hasPrefix:@"http://"]) { @@ -116,6 +120,7 @@ +(instancetype)reachabilityWithHostname:(NSString*)hostname }]; [dataTask resume]; return reachabilityInstance; +#if ODW_LEGACY_REACHABILITY_REQUIRED } // Use SCNetworkReachability for macOS 10.14 or lower @@ -131,6 +136,7 @@ +(instancetype)reachabilityWithHostname:(NSString*)hostname } return nil; +#endif } +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress @@ -140,8 +146,10 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress return nil; } +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif // Use URLSession for macOS 10.14 or higher NSString *addressString = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)hostAddress)->sin_addr)]; NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", addressString]]; @@ -153,6 +161,7 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress }]; [dataTask resume]; return reachabilityInstance; // Return the instance after resuming the data task +#if ODW_LEGACY_REACHABILITY_REQUIRED } // Use SCNetworkReachability for macOS 10.14 or lower @@ -168,6 +177,7 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress } return nil; +#endif } +(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:(NSError *)error url:(NSURL *)url @@ -284,8 +294,10 @@ -(void)dealloc -(BOOL)startNotifier { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif // Use URLSession for macOS 10.14 or higher NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *task = [session dataTaskWithURL:[self url] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { @@ -304,6 +316,7 @@ -(BOOL)startNotifier NSLog(@"Failed to create URLSessionDataTask"); return NO; } +#if ODW_LEGACY_REACHABILITY_REQUIRED } // Use SCNetworkReachability for macOS 10.14 or lower @@ -346,14 +359,19 @@ -(BOOL)startNotifier // if we get here we fail at the internet self.reachabilityObject = nil; return NO; +#endif } -(void)stopNotifier { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif // Use URLSession for macOS 10.14 or higher, no specific action is needed for URLSession self.reachabilityObject = nil; +#if ODW_LEGACY_REACHABILITY_REQUIRED + return; } // Use SCNetworkReachability for macOS 10.14 or lower @@ -366,6 +384,7 @@ -(void)stopNotifier SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); #pragma clang diagnostic pop self.reachabilityObject = nil; +#endif } @@ -408,9 +427,12 @@ -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags -(BOOL)isReachable { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif return [self checkNetworkReachability:true]; +#if ODW_LEGACY_REACHABILITY_REQUIRED } // for macOS 10.14 or lower @@ -423,11 +445,13 @@ -(BOOL)isReachable #pragma clang diagnostic pop return [self isReachableWithFlags:flags]; +#endif } -(BOOL)isReachableViaWWAN { +#if ODW_LEGACY_REACHABILITY_REQUIRED #if TARGET_OS_IPHONE SCNetworkReachabilityFlags flags = 0; @@ -447,13 +471,19 @@ -(BOOL)isReachableViaWWAN #endif return NO; +#else + return NO; +#endif } -(BOOL)isReachableViaWiFi { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif return [self checkNetworkReachability:true]; +#if ODW_LEGACY_REACHABILITY_REQUIRED } // for macOS 10.14 or lower @@ -479,6 +509,7 @@ -(BOOL)isReachableViaWiFi } return NO; +#endif } @@ -491,9 +522,12 @@ -(BOOL)isConnectionRequired -(BOOL)connectionRequired { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif return [self checkNetworkReachability:false]; +#if ODW_LEGACY_REACHABILITY_REQUIRED } // for macOS 10.14 or lower @@ -508,15 +542,19 @@ -(BOOL)connectionRequired } return NO; +#endif } // Dynamic, on demand connection? -(BOOL)isConnectionOnDemand { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif return [self checkNetworkReachability:true]; +#if ODW_LEGACY_REACHABILITY_REQUIRED } // for macOS 10.14 or lower @@ -532,15 +570,19 @@ -(BOOL)isConnectionOnDemand } return NO; +#endif } // Is user intervention required? -(BOOL)isInterventionRequired { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif return [self checkNetworkReachability:false]; +#if ODW_LEGACY_REACHABILITY_REQUIRED } // for macOS 10.14 or lower @@ -556,6 +598,7 @@ -(BOOL)isInterventionRequired } return NO; +#endif } @@ -579,11 +622,13 @@ -(ODWNetworkStatus)currentReachabilityStatus -(SCNetworkReachabilityFlags)reachabilityFlags { +#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { +#endif __block SCNetworkReachabilityFlags flags = 0; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - + NSURLSession *session = [NSURLSession sharedSession]; NSURLSessionDataTask *task = [session dataTaskWithURL:[self url] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error == nil && data != nil) { @@ -591,11 +636,12 @@ -(SCNetworkReachabilityFlags)reachabilityFlags } dispatch_semaphore_signal(semaphore); }]; - + [task resume]; dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); - + return flags; +#if ODW_LEGACY_REACHABILITY_REQUIRED } // for macOS 10.14 or lower @@ -610,6 +656,7 @@ -(SCNetworkReachabilityFlags)reachabilityFlags } return 0; +#endif } -(NSString*)currentReachabilityString From 3906d9ef0f1f6fdaff5ce6297b697439bdfdd667 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 29 Apr 2026 15:32:41 -0700 Subject: [PATCH 02/24] Restore ODWReachabilityTests includes to keep #1431 focused PR #1431 should not carry changes in ODWReachabilityTests.mm. Restore the socket header imports so the branch only contains the reachability implementation changes. Files changed: - tests/unittests/obj-c/ODWReachabilityTests.mm Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/unittests/obj-c/ODWReachabilityTests.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/unittests/obj-c/ODWReachabilityTests.mm b/tests/unittests/obj-c/ODWReachabilityTests.mm index d231bbec8..cc8a7c24e 100644 --- a/tests/unittests/obj-c/ODWReachabilityTests.mm +++ b/tests/unittests/obj-c/ODWReachabilityTests.mm @@ -11,6 +11,8 @@ #import #import "ODWReachability.h" +#import +#import #import @interface ODWReachabilityTests : XCTestCase From 12d89582375126d4382886a5d85b962603c4f991 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 29 Apr 2026 15:34:13 -0700 Subject: [PATCH 03/24] Restore ODWReachability.m header section to match main Keep PR #1431 focused on the reachability implementation changes by reverting the top-of-file/header-area edits in ODWReachability.m. Files changed: - third_party/Reachability/ODWReachability.m Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index dc8deb38f..38faf54d5 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -27,6 +27,8 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF #import "ODWReachability.h" +#import +#import #import @@ -41,8 +43,6 @@ @interface ODWReachability () -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; -+(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:(NSError *)error url:(NSURL *)url; --(SCNetworkReachabilityFlags)checkNetworkReachability:(BOOL)checkData; @end @@ -65,7 +65,6 @@ -(SCNetworkReachabilityFlags)checkNetworkReachability:(BOOL)checkData; (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; } -#if ODW_LEGACY_REACHABILITY_REQUIRED // Start listening for reachability notifications on the current run loop static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) { @@ -80,7 +79,6 @@ static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRea [reachability reachabilityChanged:flags]; } } -#endif @implementation ODWReachability From 770250810e3e0067db8340e4814fb2661513136c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 1 May 2026 19:20:16 -0500 Subject: [PATCH 04/24] Use NWPathMonitor in ODWReachability Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 341 +++++++++++++++++---- 1 file changed, 286 insertions(+), 55 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 38faf54d5..a24c4506e 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -27,22 +27,48 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF #import "ODWReachability.h" +#import + #import #import #import +#import NSString *const kNetworkReachabilityChangedNotification = @"NetworkReachabilityChangedNotification"; +@class ODWReachability; + +@interface ODWReachabilityMonitorContext : NSObject + +@property (nonatomic, assign) ODWReachability *owner; + +@end + +@implementation ODWReachabilityMonitorContext +@end + @interface ODWReachability () @property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; @property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; @property (nonatomic, strong) id reachabilityObject; +@property (nonatomic, strong) nw_path_monitor_t pathMonitor; +@property (nonatomic, strong) ODWReachabilityMonitorContext *pathMonitorContext; +@property (nonatomic, strong) dispatch_semaphore_t initialPathSemaphore; +@property (nonatomic, assign) nw_path_status_t currentPathStatus; +@property (nonatomic, assign) BOOL currentPathUsesWiFi; +@property (nonatomic, assign) BOOL currentPathUsesWWAN; +@property (nonatomic, assign) BOOL hasObservedPath; +@property (nonatomic, assign) BOOL monitorLocalWiFiOnly; -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; +-(BOOL)ensureModernPathMonitor; +-(BOOL)awaitModernPathSnapshot; +-(void)handleModernPathUpdate:(nw_path_t)path; +-(void)notifyModernPathChange; @end @@ -62,9 +88,36 @@ -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', - (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; +} + +static BOOL ODWModernPathIsReachable(nw_path_status_t status) +{ + return status == nw_path_status_satisfied || status == nw_path_status_satisfiable; +} + +static BOOL ODWHostResolves(NSString *hostname) +{ + if (hostname == nil || hostname.length == 0) + { + return NO; + } + + struct addrinfo hints = {}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo *result = NULL; + int lookupResult = getaddrinfo(hostname.UTF8String, NULL, &hints, &result); + if (result != NULL) + { + freeaddrinfo(result); + } + + return lookupResult == 0; } +#if ODW_LEGACY_REACHABILITY_REQUIRED // Start listening for reachability notifications on the current run loop static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) { @@ -79,6 +132,7 @@ static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRea [reachability reachabilityChanged:flags]; } } +#endif @implementation ODWReachability @@ -103,20 +157,20 @@ +(instancetype)reachabilityWithHostname:(NSString*)hostname if (@available(macOS 10.14, iOS 12.0, *)) { #endif - // Use URLSession for macOS 10.14 or higher + // Use Network.framework reachability for macOS 10.14 or higher. NSString *formattedHostname = hostname; if (![formattedHostname hasPrefix:@"https://"] && ![formattedHostname hasPrefix:@"http://"]) { formattedHostname = [NSString stringWithFormat:@"https://%@", hostname]; } NSURL *url = [NSURL URLWithString:formattedHostname]; + if (url == nil || url.host == nil || !ODWHostResolves(url.host)) + { + NSLog(@"Invalid hostname"); + return nil; + } - NSURLSession *session = [NSURLSession sharedSession]; - __block ODWReachability *reachabilityInstance = [[self alloc] init]; + ODWReachability *reachabilityInstance = [[self alloc] init]; reachabilityInstance.url = url; - NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - reachabilityInstance = [self handleReachabilityResponse:response error:error url:reachabilityInstance.url]; - }]; - [dataTask resume]; return reachabilityInstance; #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -148,17 +202,19 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress if (@available(macOS 10.14, iOS 12.0, *)) { #endif - // Use URLSession for macOS 10.14 or higher - NSString *addressString = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)hostAddress)->sin_addr)]; + // Use Network.framework reachability for macOS 10.14 or higher. + struct sockaddr_in *address = (struct sockaddr_in *)hostAddress; + if (address->sin_addr.s_addr == INADDR_ANY) + { + NSLog(@"Invalid address"); + return nil; + } + + NSString *addressString = [NSString stringWithUTF8String:inet_ntoa(address->sin_addr)]; NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", addressString]]; - NSURLSession *session = [NSURLSession sharedSession]; - __block ODWReachability *reachabilityInstance = [[self alloc] init]; + ODWReachability *reachabilityInstance = [[self alloc] init]; reachabilityInstance.url = url; - NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - reachabilityInstance = [self handleReachabilityResponse:response error:error url:reachabilityInstance.url]; - }]; - [dataTask resume]; - return reachabilityInstance; // Return the instance after resuming the data task + return reachabilityInstance; #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -207,6 +263,13 @@ +(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:( +(ODWReachability *)reachabilityForInternetConnection { +#if ODW_LEGACY_REACHABILITY_REQUIRED + if (@available(macOS 10.14, iOS 12.0, *)) + { + return [[self alloc] init]; + } +#endif + struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); @@ -217,6 +280,15 @@ +(ODWReachability *)reachabilityForInternetConnection +(ODWReachability*)reachabilityForLocalWiFi { +#if ODW_LEGACY_REACHABILITY_REQUIRED + if (@available(macOS 10.14, iOS 12.0, *)) + { + ODWReachability *reachability = [[self alloc] init]; + reachability.monitorLocalWiFiOnly = YES; + return reachability; + } +#endif + struct sockaddr_in localWifiAddress; bzero(&localWifiAddress, sizeof(localWifiAddress)); localWifiAddress.sin_len = sizeof(localWifiAddress); @@ -230,23 +302,139 @@ +(ODWReachability*)reachabilityForLocalWiFi // Initialization methods --(ODWReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref +-(instancetype)init { self = [super init]; if (self != nil) { self.reachableOnWWAN = YES; - self.reachabilityRef = ref; + self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); + self.currentPathStatus = nw_path_status_invalid; + } - // We need to create a serial queue. - // We allocate this once for the lifetime of the notifier. + return self; +} - self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); +-(ODWReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref +{ + self = [self init]; + if (self != nil) + { + self.reachabilityRef = ref; } return self; } +-(BOOL)ensureModernPathMonitor +{ + if (self.pathMonitor != nil) + { + return YES; + } + + self.hasObservedPath = NO; + self.currentPathStatus = nw_path_status_invalid; + self.currentPathUsesWiFi = NO; + self.currentPathUsesWWAN = NO; + self.initialPathSemaphore = dispatch_semaphore_create(0); + self.pathMonitor = self.monitorLocalWiFiOnly + ? nw_path_monitor_create_with_type(nw_interface_type_wifi) + : nw_path_monitor_create(); + + if (self.pathMonitor == nil) + { + return NO; + } + + ODWReachabilityMonitorContext *context = [[ODWReachabilityMonitorContext alloc] init]; + context.owner = self; + self.pathMonitorContext = context; +#if !__has_feature(objc_arc) + [context release]; +#endif + + nw_path_monitor_set_queue(self.pathMonitor, self.reachabilitySerialQueue); + nw_path_monitor_set_update_handler(self.pathMonitor, ^(nw_path_t path) { + ODWReachability *owner = context.owner; + if (owner == nil) + { + return; + } + + [owner handleModernPathUpdate:path]; + }); + nw_path_monitor_start(self.pathMonitor); + + return YES; +} + +-(BOOL)awaitModernPathSnapshot +{ + if (![self ensureModernPathMonitor]) + { + return NO; + } + + if (self.hasObservedPath) + { + return YES; + } + + if (self.initialPathSemaphore == nil) + { + return NO; + } + + long waitResult = dispatch_semaphore_wait( + self.initialPathSemaphore, + dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); + return waitResult == 0 && self.hasObservedPath; +} + +-(void)handleModernPathUpdate:(nw_path_t)path +{ + self.currentPathStatus = nw_path_get_status(path); + self.currentPathUsesWiFi = nw_path_uses_interface_type(path, nw_interface_type_wifi); +#if TARGET_OS_IPHONE + self.currentPathUsesWWAN = nw_path_uses_interface_type(path, nw_interface_type_cellular); +#else + self.currentPathUsesWWAN = NO; +#endif + + BOOL firstPath = !self.hasObservedPath; + self.hasObservedPath = YES; + if (firstPath && self.initialPathSemaphore != nil) + { + dispatch_semaphore_signal(self.initialPathSemaphore); + } + + if (self.reachabilityObject == self) + { + [self notifyModernPathChange]; + } +} + +-(void)notifyModernPathChange +{ + if (ODWModernPathIsReachable(self.currentPathStatus)) + { + if (self.reachableBlock) + { + self.reachableBlock(self); + } + } + else if (self.unreachableBlock) + { + self.unreachableBlock(self); + } + + dispatch_async(dispatch_get_main_queue(), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkReachabilityChangedNotification + object:self]; + }); +} + +(void)setTimeoutDurationInSeconds:(int)timeoutDuration { if (timeoutDuration >= kTimeoutDurationInSeconds) @@ -296,24 +484,17 @@ -(BOOL)startNotifier if (@available(macOS 10.14, iOS 12.0, *)) { #endif - // Use URLSession for macOS 10.14 or higher - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDataTask *task = [session dataTaskWithURL:[self url] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (error) { - NSLog(@"URLSession failed: %@", error.localizedDescription); - self.reachabilityObject = nil; - } else { - self.reachabilityObject = self; - [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkReachabilityChangedNotification object:self]; + // Use NWPathMonitor for macOS 10.14 or higher. + if ([self ensureModernPathMonitor]) + { + self.reachabilityObject = self; + if ([self awaitModernPathSnapshot]) + { + [self notifyModernPathChange]; } - }]; - if (task) { - [task resume]; return YES; - } else { - NSLog(@"Failed to create URLSessionDataTask"); - return NO; } + return NO; #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -366,8 +547,20 @@ -(void)stopNotifier if (@available(macOS 10.14, iOS 12.0, *)) { #endif - // Use URLSession for macOS 10.14 or higher, no specific action is needed for URLSession + // Use NWPathMonitor for macOS 10.14 or higher. self.reachabilityObject = nil; + if (self.pathMonitor != nil) + { + self.pathMonitorContext.owner = nil; + nw_path_monitor_cancel(self.pathMonitor); + self.pathMonitor = nil; + } + self.pathMonitorContext = nil; + self.initialPathSemaphore = nil; + self.hasObservedPath = NO; + self.currentPathStatus = nw_path_status_invalid; + self.currentPathUsesWiFi = NO; + self.currentPathUsesWWAN = NO; #if ODW_LEGACY_REACHABILITY_REQUIRED return; } @@ -429,7 +622,7 @@ -(BOOL)isReachable if (@available(macOS 10.14, iOS 12.0, *)) { #endif - return [self checkNetworkReachability:true]; + return [self awaitModernPathSnapshot] && ODWModernPathIsReachable(self.currentPathStatus); #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -450,6 +643,17 @@ -(BOOL)isReachable -(BOOL)isReachableViaWWAN { #if ODW_LEGACY_REACHABILITY_REQUIRED + if (@available(macOS 10.14, iOS 12.0, *)) + { +#if TARGET_OS_IPHONE + return [self awaitModernPathSnapshot] && + ODWModernPathIsReachable(self.currentPathStatus) && + self.currentPathUsesWWAN; +#else + return NO; +#endif + } + #if TARGET_OS_IPHONE SCNetworkReachabilityFlags flags = 0; @@ -469,9 +673,16 @@ -(BOOL)isReachableViaWWAN #endif return NO; +#endif +#if !ODW_LEGACY_REACHABILITY_REQUIRED +#if TARGET_OS_IPHONE + return [self awaitModernPathSnapshot] && + ODWModernPathIsReachable(self.currentPathStatus) && + self.currentPathUsesWWAN; #else return NO; #endif +#endif } -(BOOL)isReachableViaWiFi @@ -480,7 +691,20 @@ -(BOOL)isReachableViaWiFi if (@available(macOS 10.14, iOS 12.0, *)) { #endif - return [self checkNetworkReachability:true]; + if (![self awaitModernPathSnapshot] || !ODWModernPathIsReachable(self.currentPathStatus)) + { + return NO; + } +#if TARGET_OS_IPHONE + if (self.monitorLocalWiFiOnly) + { + return self.currentPathUsesWiFi; + } + + return !self.currentPathUsesWWAN; +#else + return self.monitorLocalWiFiOnly ? self.currentPathUsesWiFi : YES; +#endif #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -524,7 +748,8 @@ -(BOOL)connectionRequired if (@available(macOS 10.14, iOS 12.0, *)) { #endif - return [self checkNetworkReachability:false]; + return [self awaitModernPathSnapshot] && + self.currentPathStatus == nw_path_status_satisfiable; #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -551,7 +776,7 @@ -(BOOL)isConnectionOnDemand if (@available(macOS 10.14, iOS 12.0, *)) { #endif - return [self checkNetworkReachability:true]; + return NO; #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -579,7 +804,7 @@ -(BOOL)isInterventionRequired if (@available(macOS 10.14, iOS 12.0, *)) { #endif - return [self checkNetworkReachability:false]; + return NO; #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -624,20 +849,26 @@ -(SCNetworkReachabilityFlags)reachabilityFlags if (@available(macOS 10.14, iOS 12.0, *)) { #endif - __block SCNetworkReachabilityFlags flags = 0; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDataTask *task = [session dataTaskWithURL:[self url] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (error == nil && data != nil) { - flags = kSCNetworkReachabilityFlagsReachable; + if (![self awaitModernPathSnapshot]) + { + return 0; } - dispatch_semaphore_signal(semaphore); - }]; - - [task resume]; - dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); + SCNetworkReachabilityFlags flags = 0; + if (ODWModernPathIsReachable(self.currentPathStatus)) + { + flags |= kSCNetworkReachabilityFlagsReachable; + } + if (self.currentPathStatus == nw_path_status_satisfiable) + { + flags |= kSCNetworkReachabilityFlagsConnectionRequired; + } +#if TARGET_OS_IPHONE + if (self.currentPathUsesWWAN) + { + flags |= kSCNetworkReachabilityFlagsIsWWAN; + } +#endif return flags; #if ODW_LEGACY_REACHABILITY_REQUIRED } From 675cad4e2dcab9803fcc3f2847ff52522a5eeee3 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 2 May 2026 00:38:54 -0500 Subject: [PATCH 05/24] Clarify modern WWAN reachability handling Make the modern ODWReachability WWAN path explicit so iOS 12+ builds unambiguously use the NWPathMonitor-backed state, while the legacy SCNetworkReachability fallback remains only for older Apple deployment targets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index a24c4506e..f7d6aa158 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -642,20 +642,16 @@ -(BOOL)isReachable -(BOOL)isReachableViaWWAN { +#if TARGET_OS_IPHONE + BOOL modernWWANReachable = [self awaitModernPathSnapshot] && + ODWModernPathIsReachable(self.currentPathStatus) && + self.currentPathUsesWWAN; #if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { -#if TARGET_OS_IPHONE - return [self awaitModernPathSnapshot] && - ODWModernPathIsReachable(self.currentPathStatus) && - self.currentPathUsesWWAN; -#else - return NO; -#endif + return modernWWANReachable; } -#if TARGET_OS_IPHONE - SCNetworkReachabilityFlags flags = 0; if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) @@ -670,19 +666,15 @@ -(BOOL)isReachableViaWWAN } } } -#endif return NO; #endif #if !ODW_LEGACY_REACHABILITY_REQUIRED -#if TARGET_OS_IPHONE - return [self awaitModernPathSnapshot] && - ODWModernPathIsReachable(self.currentPathStatus) && - self.currentPathUsesWWAN; + return modernWWANReachable; +#endif #else return NO; #endif -#endif } -(BOOL)isReachableViaWiFi From 366f87a7b18a4bc5139e123e9f3dca187a793ded Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 2 May 2026 16:34:04 -0500 Subject: [PATCH 06/24] Avoid blocking Apple reachability construction Modern reachability should not synchronously resolve DNS or reject the generic internet reachability address, and the path monitor context must not hold a stale raw owner pointer. Also avoid blocking the main thread while waiting for the first NWPathMonitor snapshot. Files changed: - third_party/Reachability/ODWReachability.m Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 41 ++++++++-------------- 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index f7d6aa158..76a040648 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -32,7 +32,6 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF #import #import #import -#import NSString *const kNetworkReachabilityChangedNotification = @"NetworkReachabilityChangedNotification"; @@ -41,7 +40,11 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF @interface ODWReachabilityMonitorContext : NSObject -@property (nonatomic, assign) ODWReachability *owner; +#if __has_feature(objc_arc) +@property (nonatomic, weak) ODWReachability *owner; +#else +@property (nonatomic, unsafe_unretained) ODWReachability *owner; +#endif @end @@ -96,27 +99,6 @@ static BOOL ODWModernPathIsReachable(nw_path_status_t status) return status == nw_path_status_satisfied || status == nw_path_status_satisfiable; } -static BOOL ODWHostResolves(NSString *hostname) -{ - if (hostname == nil || hostname.length == 0) - { - return NO; - } - - struct addrinfo hints = {}; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - struct addrinfo *result = NULL; - int lookupResult = getaddrinfo(hostname.UTF8String, NULL, &hints, &result); - if (result != NULL) - { - freeaddrinfo(result); - } - - return lookupResult == 0; -} - #if ODW_LEGACY_REACHABILITY_REQUIRED // Start listening for reachability notifications on the current run loop static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) @@ -163,7 +145,7 @@ +(instancetype)reachabilityWithHostname:(NSString*)hostname formattedHostname = [NSString stringWithFormat:@"https://%@", hostname]; } NSURL *url = [NSURL URLWithString:formattedHostname]; - if (url == nil || url.host == nil || !ODWHostResolves(url.host)) + if (url == nil || url.host == nil) { NSLog(@"Invalid hostname"); return nil; @@ -206,8 +188,7 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress struct sockaddr_in *address = (struct sockaddr_in *)hostAddress; if (address->sin_addr.s_addr == INADDR_ANY) { - NSLog(@"Invalid address"); - return nil; + return [[self alloc] init]; } NSString *addressString = [NSString stringWithUTF8String:inet_ntoa(address->sin_addr)]; @@ -386,6 +367,14 @@ -(BOOL)awaitModernPathSnapshot return NO; } + // Avoid blocking reachability queries on the main thread before the first + // NWPathMonitor update arrives. Callers get a conservative "unknown yet" + // result until the async update handler records the first snapshot. + if ([NSThread isMainThread]) + { + return NO; + } + long waitResult = dispatch_semaphore_wait( self.initialPathSemaphore, dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); From c13c90c35819070bd874aea4a0e25db092d06490 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 5 May 2026 11:11:24 -0500 Subject: [PATCH 07/24] Gate ODWReachability NWPathMonitor calls by availability Annotate the modern-path helpers (`ODWModernPathIsReachable`, `ensureModernPathMonitor`, `awaitModernPathSnapshot`, `handleModernPathUpdate:`, `notifyModernPathChange`) with `API_AVAILABLE(macos(10.14), ios(12.0))` so that compiling against an older deployment target no longer triggers `-Wunguarded-availability-new` errors under `-Werror`. Also fix a latent bug in `isReachableViaWWAN` where the modern-path snapshot was computed before the `if (@available(...))` guard. On macOS 10.10 / iOS 10.0 deployment targets running on a host that lacks NWPathMonitor, this would invoke unavailable Network framework APIs. The modern-path branch is now only taken inside the `@available` block on legacy builds, mirroring the structure used in `isReachableViaWiFi`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 76a040648..bff596849 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -68,10 +68,10 @@ @interface ODWReachability () -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; --(BOOL)ensureModernPathMonitor; --(BOOL)awaitModernPathSnapshot; --(void)handleModernPathUpdate:(nw_path_t)path; --(void)notifyModernPathChange; +-(BOOL)ensureModernPathMonitor API_AVAILABLE(macos(10.14), ios(12.0)); +-(BOOL)awaitModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); +-(void)handleModernPathUpdate:(nw_path_t)path API_AVAILABLE(macos(10.14), ios(12.0)); +-(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0)); @end @@ -94,7 +94,7 @@ -(void)notifyModernPathChange; (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; } -static BOOL ODWModernPathIsReachable(nw_path_status_t status) +static BOOL ODWModernPathIsReachable(nw_path_status_t status) API_AVAILABLE(macos(10.14), ios(12.0)) { return status == nw_path_status_satisfied || status == nw_path_status_satisfiable; } @@ -632,13 +632,12 @@ -(BOOL)isReachable -(BOOL)isReachableViaWWAN { #if TARGET_OS_IPHONE - BOOL modernWWANReachable = [self awaitModernPathSnapshot] && - ODWModernPathIsReachable(self.currentPathStatus) && - self.currentPathUsesWWAN; #if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { - return modernWWANReachable; + return [self awaitModernPathSnapshot] && + ODWModernPathIsReachable(self.currentPathStatus) && + self.currentPathUsesWWAN; } SCNetworkReachabilityFlags flags = 0; @@ -657,9 +656,10 @@ -(BOOL)isReachableViaWWAN } return NO; -#endif -#if !ODW_LEGACY_REACHABILITY_REQUIRED - return modernWWANReachable; +#else + return [self awaitModernPathSnapshot] && + ODWModernPathIsReachable(self.currentPathStatus) && + self.currentPathUsesWWAN; #endif #else return NO; From 09a73ab775b50e6d2474a26614431c7a7acb2f39 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 5 May 2026 19:23:39 -0500 Subject: [PATCH 08/24] Capture ODWReachability semaphore before signal/wait `-handleModernPathUpdate:` and `-awaitModernPathSnapshot` previously read `self.initialPathSemaphore` more than once across the nil-check and the matching `dispatch_semaphore_signal`/`dispatch_semaphore_wait` call. `-stopNotifier` clears the property from an arbitrary thread, so the second read could observe nil and pass it into libdispatch (which crashes on a nil semaphore). Capture the semaphore into a strong local once. Under ARC the local retains the dispatch_semaphore_t for the duration of the call, so a concurrent stop will no longer race the signal/wait. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index bff596849..c86217c7f 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -362,7 +362,11 @@ -(BOOL)awaitModernPathSnapshot return YES; } - if (self.initialPathSemaphore == nil) + // Capture the semaphore into a local so a concurrent -stopNotifier on + // another thread cannot release the property between the nil-check and + // the wait below. + dispatch_semaphore_t semaphore = self.initialPathSemaphore; + if (semaphore == nil) { return NO; } @@ -376,7 +380,7 @@ -(BOOL)awaitModernPathSnapshot } long waitResult = dispatch_semaphore_wait( - self.initialPathSemaphore, + semaphore, dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); return waitResult == 0 && self.hasObservedPath; } @@ -393,9 +397,16 @@ -(void)handleModernPathUpdate:(nw_path_t)path BOOL firstPath = !self.hasObservedPath; self.hasObservedPath = YES; - if (firstPath && self.initialPathSemaphore != nil) + if (firstPath) { - dispatch_semaphore_signal(self.initialPathSemaphore); + // Capture the semaphore into a local so a concurrent -stopNotifier + // on another thread cannot release the property between the + // nil-check and the signal below. + dispatch_semaphore_t semaphore = self.initialPathSemaphore; + if (semaphore != nil) + { + dispatch_semaphore_signal(semaphore); + } } if (self.reachabilityObject == self) From a5de13d35f325096ca68a7b58ec1bf3ddc10cef5 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 11 May 2026 13:03:22 -0500 Subject: [PATCH 09/24] Address reachability review comments Preserve host/address-specific reachability semantics with SCNetworkReachability while keeping Network.framework for general path monitoring. Avoid waiting for the first NWPathMonitor update on the monitor queue, and use platform-specific Apple availability gates. Files changed: - third_party/Reachability/ODWReachability.h - third_party/Reachability/ODWReachability.m Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.h | 27 +- third_party/Reachability/ODWReachability.m | 390 +++++++++++---------- 2 files changed, 236 insertions(+), 181 deletions(-) diff --git a/third_party/Reachability/ODWReachability.h b/third_party/Reachability/ODWReachability.h index 57f3702f9..9a63e42c9 100644 --- a/third_party/Reachability/ODWReachability.h +++ b/third_party/Reachability/ODWReachability.h @@ -44,14 +44,39 @@ extern NSString* const kNetworkReachabilityChangedNotification; // Older Apple deployment targets still need the legacy SCNetworkReachability // backend at runtime. Newer targets can compile directly to the modern path. -#if TARGET_OS_IPHONE +#ifndef TARGET_OS_IOS +#define TARGET_OS_IOS TARGET_OS_IPHONE +#endif +#ifndef TARGET_OS_MACCATALYST +#define TARGET_OS_MACCATALYST 0 +#endif +#ifndef TARGET_OS_TV +#define TARGET_OS_TV 0 +#endif +#ifndef TARGET_OS_WATCH +#define TARGET_OS_WATCH 0 +#endif +#ifndef __TVOS_12_0 +#define __TVOS_12_0 120000 +#endif +#ifndef __WATCHOS_5_0 +#define __WATCHOS_5_0 50000 +#endif + +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST #define ODW_LEGACY_REACHABILITY_REQUIRED (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0) +#elif TARGET_OS_TV +#define ODW_LEGACY_REACHABILITY_REQUIRED (__TV_OS_VERSION_MIN_REQUIRED < __TVOS_12_0) +#elif TARGET_OS_WATCH +#define ODW_LEGACY_REACHABILITY_REQUIRED (__WATCH_OS_VERSION_MIN_REQUIRED < __WATCHOS_5_0) #elif TARGET_OS_OSX #define ODW_LEGACY_REACHABILITY_REQUIRED (__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_14) #else #define ODW_LEGACY_REACHABILITY_REQUIRED 0 #endif +#define ODW_REACHABILITY_HAS_WWAN (TARGET_OS_IOS || TARGET_OS_MACCATALYST) + typedef NS_ENUM(NSInteger, ODWNetworkStatus) { // Apple NetworkStatus Compatible Names. NotReachable = 0, diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index c86217c7f..da6fe9967 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -35,6 +35,7 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF NSString *const kNetworkReachabilityChangedNotification = @"NetworkReachabilityChangedNotification"; +static char ODWReachabilityQueueKey; @class ODWReachability; @@ -68,10 +69,13 @@ @interface ODWReachability () -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; --(BOOL)ensureModernPathMonitor API_AVAILABLE(macos(10.14), ios(12.0)); --(BOOL)awaitModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); --(void)handleModernPathUpdate:(nw_path_t)path API_AVAILABLE(macos(10.14), ios(12.0)); --(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0)); +-(BOOL)getReachabilityFlags:(SCNetworkReachabilityFlags *)flags; +-(BOOL)startLegacyNotifier; +-(void)stopLegacyNotifier; +-(BOOL)ensureModernPathMonitor API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)); +-(BOOL)awaitModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)); +-(void)handleModernPathUpdate:(nw_path_t)path API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)); +-(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)); @end @@ -79,7 +83,7 @@ -(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0)); static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) { return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", -#if TARGET_OS_IPHONE +#if ODW_REACHABILITY_HAS_WWAN (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', #else 'X', @@ -94,12 +98,11 @@ -(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0)); (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; } -static BOOL ODWModernPathIsReachable(nw_path_status_t status) API_AVAILABLE(macos(10.14), ios(12.0)) +static BOOL ODWModernPathIsReachable(nw_path_status_t status) API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) { return status == nw_path_status_satisfied || status == nw_path_status_satisfiable; } -#if ODW_LEGACY_REACHABILITY_REQUIRED // Start listening for reachability notifications on the current run loop static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) { @@ -114,7 +117,6 @@ static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkRea [reachability reachabilityChanged:flags]; } } -#endif @implementation ODWReachability @@ -125,94 +127,116 @@ @implementation ODWReachability +(ODWReachability*)reachabilityWithHostName:(NSString*)hostname { - if (hostname == nil || [hostname length] == 0) - { - NSLog(@"Invalid hostname"); - return nil; - } return [ODWReachability reachabilityWithHostname:hostname]; } +(instancetype)reachabilityWithHostname:(NSString*)hostname { -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (hostname == nil || [hostname length] == 0) { -#endif - // Use Network.framework reachability for macOS 10.14 or higher. - NSString *formattedHostname = hostname; - if (![formattedHostname hasPrefix:@"https://"] && ![formattedHostname hasPrefix:@"http://"]) { - formattedHostname = [NSString stringWithFormat:@"https://%@", hostname]; - } - NSURL *url = [NSURL URLWithString:formattedHostname]; - if (url == nil || url.host == nil) + NSLog(@"Invalid hostname '%@': hostname is empty", hostname); + return nil; + } + + NSString *reachabilityHost = hostname; + NSURL *url = nil; + NSURLComponents *components = [NSURLComponents componentsWithString:hostname]; + if ([components.scheme length] > 0) + { + if ([components.host length] == 0) { - NSLog(@"Invalid hostname"); + NSLog(@"Invalid hostname '%@': URL has no host", hostname); return nil; } - ODWReachability *reachabilityInstance = [[self alloc] init]; - reachabilityInstance.url = url; - return reachabilityInstance; -#if ODW_LEGACY_REACHABILITY_REQUIRED + reachabilityHost = components.host; + url = components.URL; + } + + if (url == nil) + { + url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", reachabilityHost]]; } - // Use SCNetworkReachability for macOS 10.14 or lower #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [hostname UTF8String]); + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [reachabilityHost UTF8String]); #pragma clang diagnostic pop if (ref) { - id reachability = [[self alloc] initWithReachabilityRef:ref]; + ODWReachability *reachability = [[self alloc] initWithReachabilityRef:ref]; + reachability.url = url; return reachability; } + const char *errorString = SCErrorString(SCError()); + NSLog(@"Invalid hostname '%@': SCNetworkReachabilityCreateWithName failed for '%@' (%s)", + hostname, + reachabilityHost, + errorString != NULL ? errorString : "unknown error"); return nil; -#endif } +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress { - if (hostAddress == NULL) { - NSLog(@"Invalid address"); + if (hostAddress == NULL) + { + NSLog(@"Invalid address: address pointer is null"); return nil; } -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + struct sockaddr_storage addressStorage; + bzero(&addressStorage, sizeof(addressStorage)); + struct sockaddr *address = (struct sockaddr *)hostAddress; + NSURL *url = nil; + if (address->sa_family == AF_INET) { -#endif - // Use Network.framework reachability for macOS 10.14 or higher. - struct sockaddr_in *address = (struct sockaddr_in *)hostAddress; - if (address->sin_addr.s_addr == INADDR_ANY) + char addressString[INET_ADDRSTRLEN] = { 0 }; + struct sockaddr_in *ipv4Address = (struct sockaddr_in *)&addressStorage; + *ipv4Address = *(struct sockaddr_in *)hostAddress; + if (ipv4Address->sin_len == 0) { - return [[self alloc] init]; + ipv4Address->sin_len = sizeof(*ipv4Address); + } + address = (struct sockaddr *)ipv4Address; + if (inet_ntop(AF_INET, &ipv4Address->sin_addr, addressString, sizeof(addressString)) != NULL) + { + url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%s", addressString]]; } - - NSString *addressString = [NSString stringWithUTF8String:inet_ntoa(address->sin_addr)]; - NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", addressString]]; - ODWReachability *reachabilityInstance = [[self alloc] init]; - reachabilityInstance.url = url; - return reachabilityInstance; -#if ODW_LEGACY_REACHABILITY_REQUIRED } - - // Use SCNetworkReachability for macOS 10.14 or lower + else if (address->sa_family == AF_INET6) + { + char addressString[INET6_ADDRSTRLEN] = { 0 }; + struct sockaddr_in6 *ipv6Address = (struct sockaddr_in6 *)&addressStorage; + *ipv6Address = *(struct sockaddr_in6 *)hostAddress; + if (ipv6Address->sin6_len == 0) + { + ipv6Address->sin6_len = sizeof(*ipv6Address); + } + address = (struct sockaddr *)ipv6Address; + if (inet_ntop(AF_INET6, &ipv6Address->sin6_addr, addressString, sizeof(addressString)) != NULL) + { + url = [NSURL URLWithString:[NSString stringWithFormat:@"https://[%s]", addressString]]; + } + } + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)hostAddress); + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)address); #pragma clang diagnostic pop if (ref) { - id reachability = [[self alloc] initWithReachabilityRef:ref]; + ODWReachability *reachability = [[self alloc] initWithReachabilityRef:ref]; + reachability.url = url; return reachability; } + const char *errorString = SCErrorString(SCError()); + NSLog(@"Invalid address: SCNetworkReachabilityCreateWithAddress failed (%s)", + errorString != NULL ? errorString : "unknown error"); return nil; -#endif } +(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:(NSError *)error url:(NSURL *)url @@ -245,10 +269,12 @@ +(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:( +(ODWReachability *)reachabilityForInternetConnection { #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { return [[self alloc] init]; } +#else + return [[self alloc] init]; #endif struct sockaddr_in zeroAddress; @@ -262,12 +288,16 @@ +(ODWReachability *)reachabilityForInternetConnection +(ODWReachability*)reachabilityForLocalWiFi { #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { ODWReachability *reachability = [[self alloc] init]; reachability.monitorLocalWiFiOnly = YES; return reachability; } +#else + ODWReachability *reachability = [[self alloc] init]; + reachability.monitorLocalWiFiOnly = YES; + return reachability; #endif struct sockaddr_in localWifiAddress; @@ -290,6 +320,10 @@ -(instancetype)init { self.reachableOnWWAN = YES; self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); + dispatch_queue_set_specific(self.reachabilitySerialQueue, + &ODWReachabilityQueueKey, + &ODWReachabilityQueueKey, + NULL); self.currentPathStatus = nw_path_status_invalid; } @@ -378,6 +412,11 @@ -(BOOL)awaitModernPathSnapshot { return NO; } + // The update handler runs on this serial queue, so waiting here would deadlock it. + if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) + { + return NO; + } long waitResult = dispatch_semaphore_wait( semaphore, @@ -389,7 +428,7 @@ -(void)handleModernPathUpdate:(nw_path_t)path { self.currentPathStatus = nw_path_get_status(path); self.currentPathUsesWiFi = nw_path_uses_interface_type(path, nw_interface_type_wifi); -#if TARGET_OS_IPHONE +#if ODW_REACHABILITY_HAS_WWAN self.currentPathUsesWWAN = nw_path_uses_interface_type(path, nw_interface_type_cellular); #else self.currentPathUsesWWAN = NO; @@ -480,8 +519,13 @@ -(void)dealloc -(BOOL)startNotifier { + if (self.reachabilityRef != nil) + { + return [self startLegacyNotifier]; + } + #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { #endif // Use NWPathMonitor for macOS 10.14 or higher. @@ -497,9 +541,14 @@ -(BOOL)startNotifier return NO; #if ODW_LEGACY_REACHABILITY_REQUIRED } - - // Use SCNetworkReachability for macOS 10.14 or lower - // allow start notifier to be called multiple times + + return NO; +#endif +} + +-(BOOL)startLegacyNotifier +{ + // Allow start notifier to be called multiple times. if (self.reachabilityObject && (self.reachabilityObject == self)) { return YES; @@ -509,10 +558,12 @@ -(BOOL)startNotifier context.info = (__bridge void *)self; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context)) - { - if (SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue)) + BOOL callbackSet = SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context); + BOOL queueSet = callbackSet && SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue); #pragma clang diagnostic pop + if (callbackSet) + { + if (queueSet) { self.reachabilityObject = self; return YES; @@ -538,13 +589,18 @@ -(BOOL)startNotifier // if we get here we fail at the internet self.reachabilityObject = nil; return NO; -#endif } -(void)stopNotifier { + if (self.reachabilityRef != nil) + { + [self stopLegacyNotifier]; + return; + } + #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { #endif // Use NWPathMonitor for macOS 10.14 or higher. @@ -565,7 +621,12 @@ -(void)stopNotifier return; } - // Use SCNetworkReachability for macOS 10.14 or lower + return; +#endif +} + +-(void)stopLegacyNotifier +{ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // First stop, any callbacks! @@ -575,7 +636,6 @@ -(void)stopNotifier SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); #pragma clang diagnostic pop self.reachabilityObject = nil; -#endif } @@ -601,7 +661,7 @@ -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags if( (flags & testcase) == testcase ) connectionUP = NO; -#if TARGET_OS_IPHONE +#if ODW_REACHABILITY_HAS_WWAN if(flags & kSCNetworkReachabilityFlagsIsWWAN) { // We're on 3G. @@ -616,61 +676,63 @@ -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags return connectionUP; } +-(BOOL)getReachabilityFlags:(SCNetworkReachabilityFlags *)flags +{ + if (self.reachabilityRef == nil) + { + return NO; + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + BOOL result = SCNetworkReachabilityGetFlags(self.reachabilityRef, flags); +#pragma clang diagnostic pop + return result; +} + -(BOOL)isReachable { + if (self.reachabilityRef != nil) + { + SCNetworkReachabilityFlags flags; + return [self getReachabilityFlags:&flags] && [self isReachableWithFlags:flags]; + } + #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { #endif return [self awaitModernPathSnapshot] && ODWModernPathIsReachable(self.currentPathStatus); #if ODW_LEGACY_REACHABILITY_REQUIRED } - // for macOS 10.14 or lower - SCNetworkReachabilityFlags flags; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if(!SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) - return NO; -#pragma clang diagnostic pop - - return [self isReachableWithFlags:flags]; + return NO; #endif } -(BOOL)isReachableViaWWAN { -#if TARGET_OS_IPHONE +#if ODW_REACHABILITY_HAS_WWAN + if (self.reachabilityRef != nil) + { + SCNetworkReachabilityFlags flags = 0; + return [self getReachabilityFlags:&flags] && + (flags & kSCNetworkReachabilityFlagsReachable) && + (flags & kSCNetworkReachabilityFlagsIsWWAN); + } + #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { +#endif return [self awaitModernPathSnapshot] && ODWModernPathIsReachable(self.currentPathStatus) && self.currentPathUsesWWAN; - } - - SCNetworkReachabilityFlags flags = 0; - - if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) - { - // Check we're REACHABLE - if(flags & kSCNetworkReachabilityFlagsReachable) - { - // Now, check we're on WWAN - if(flags & kSCNetworkReachabilityFlagsIsWWAN) - { - return YES; - } - } +#if ODW_LEGACY_REACHABILITY_REQUIRED } return NO; -#else - return [self awaitModernPathSnapshot] && - ODWModernPathIsReachable(self.currentPathStatus) && - self.currentPathUsesWWAN; #endif #else return NO; @@ -679,15 +741,32 @@ -(BOOL)isReachableViaWWAN -(BOOL)isReachableViaWiFi { + if (self.reachabilityRef != nil) + { + SCNetworkReachabilityFlags flags = 0; + if ([self getReachabilityFlags:&flags] && (flags & kSCNetworkReachabilityFlagsReachable)) + { +#if ODW_REACHABILITY_HAS_WWAN + if (flags & kSCNetworkReachabilityFlagsIsWWAN) + { + return NO; + } +#endif + return YES; + } + + return NO; + } + #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { #endif if (![self awaitModernPathSnapshot] || !ODWModernPathIsReachable(self.currentPathStatus)) { return NO; } -#if TARGET_OS_IPHONE +#if ODW_REACHABILITY_HAS_WWAN if (self.monitorLocalWiFiOnly) { return self.currentPathUsesWiFi; @@ -699,28 +778,6 @@ -(BOOL)isReachableViaWiFi #endif #if ODW_LEGACY_REACHABILITY_REQUIRED } - - // for macOS 10.14 or lower - SCNetworkReachabilityFlags flags = 0; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) -#pragma clang diagnostic pop - { - // Check we're reachable - if((flags & kSCNetworkReachabilityFlagsReachable)) - { -#if TARGET_OS_IPHONE - // Check we're NOT on WWAN - if((flags & kSCNetworkReachabilityFlagsIsWWAN)) - { - return NO; - } -#endif - return YES; - } - } return NO; #endif @@ -736,25 +793,21 @@ -(BOOL)isConnectionRequired -(BOOL)connectionRequired { + if (self.reachabilityRef != nil) + { + SCNetworkReachabilityFlags flags; + return [self getReachabilityFlags:&flags] && + (flags & kSCNetworkReachabilityFlagsConnectionRequired); + } + #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { #endif return [self awaitModernPathSnapshot] && self.currentPathStatus == nw_path_status_satisfiable; #if ODW_LEGACY_REACHABILITY_REQUIRED } - - // for macOS 10.14 or lower - SCNetworkReachabilityFlags flags; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if(SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) -#pragma clang diagnostic pop - { - return (flags & kSCNetworkReachabilityFlagsConnectionRequired); - } return NO; #endif @@ -764,56 +817,38 @@ -(BOOL)connectionRequired // Dynamic, on demand connection? -(BOOL)isConnectionOnDemand { -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (self.reachabilityRef != nil) { -#endif - return NO; -#if ODW_LEGACY_REACHABILITY_REQUIRED - } - - // for macOS 10.14 or lower - SCNetworkReachabilityFlags flags; + SCNetworkReachabilityFlags flags; + if (![self getReachabilityFlags:&flags]) + { + return NO; + } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) -#pragma clang diagnostic pop - { return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); } return NO; -#endif } // Is user intervention required? -(BOOL)isInterventionRequired { -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (self.reachabilityRef != nil) { -#endif - return NO; -#if ODW_LEGACY_REACHABILITY_REQUIRED - } - - // for macOS 10.14 or lower - SCNetworkReachabilityFlags flags; + SCNetworkReachabilityFlags flags; + if (![self getReachabilityFlags:&flags]) + { + return NO; + } -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) -#pragma clang diagnostic pop - { return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && (flags & kSCNetworkReachabilityFlagsInterventionRequired)); } return NO; -#endif } @@ -827,7 +862,7 @@ -(ODWNetworkStatus)currentReachabilityStatus if([self isReachableViaWiFi]) return ReachableViaWiFi; -#if TARGET_OS_IPHONE +#if ODW_REACHABILITY_HAS_WWAN return ReachableViaWWAN; #endif } @@ -837,8 +872,14 @@ -(ODWNetworkStatus)currentReachabilityStatus -(SCNetworkReachabilityFlags)reachabilityFlags { + if (self.reachabilityRef != nil) + { + SCNetworkReachabilityFlags flags = 0; + return [self getReachabilityFlags:&flags] ? flags : 0; + } + #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) + if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) { #endif if (![self awaitModernPathSnapshot]) @@ -855,7 +896,7 @@ -(SCNetworkReachabilityFlags)reachabilityFlags { flags |= kSCNetworkReachabilityFlagsConnectionRequired; } -#if TARGET_OS_IPHONE +#if ODW_REACHABILITY_HAS_WWAN if (self.currentPathUsesWWAN) { flags |= kSCNetworkReachabilityFlagsIsWWAN; @@ -865,17 +906,6 @@ -(SCNetworkReachabilityFlags)reachabilityFlags #if ODW_LEGACY_REACHABILITY_REQUIRED } - // for macOS 10.14 or lower - SCNetworkReachabilityFlags flags = 0; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - if (SCNetworkReachabilityGetFlags(self.reachabilityRef, &flags)) -#pragma clang diagnostic pop - { - return flags; - } - return 0; #endif } From 47114177afa5c06e03bd9fec70aa5ee4672e2857 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 11 May 2026 13:04:00 -0500 Subject: [PATCH 10/24] Avoid unguarded NWPath status init Leave the Network.framework status initialization in modern monitor setup so older Apple deployment-target builds do not touch an availability-gated symbol from init. Files changed: - third_party/Reachability/ODWReachability.m Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 1 - 1 file changed, 1 deletion(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index da6fe9967..aa1aa1b4c 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -324,7 +324,6 @@ -(instancetype)init &ODWReachabilityQueueKey, &ODWReachabilityQueueKey, NULL); - self.currentPathStatus = nw_path_status_invalid; } return self; From c9288d1b2db3445ce46aec0e10d83ba09e052d26 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 11 May 2026 14:06:40 -0500 Subject: [PATCH 11/24] Limit reachability gates to supported Apple targets Remove tvOS and watchOS deployment-target handling because the SDK does not support those targets; keep the availability gates focused on iOS and macOS. Files changed: - third_party/Reachability/ODWReachability.h - third_party/Reachability/ODWReachability.m Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.h | 25 +++---------------- third_party/Reachability/ODWReachability.m | 28 +++++++++++----------- 2 files changed, 17 insertions(+), 36 deletions(-) diff --git a/third_party/Reachability/ODWReachability.h b/third_party/Reachability/ODWReachability.h index 9a63e42c9..190d4b9d8 100644 --- a/third_party/Reachability/ODWReachability.h +++ b/third_party/Reachability/ODWReachability.h @@ -45,37 +45,18 @@ extern NSString* const kNetworkReachabilityChangedNotification; // Older Apple deployment targets still need the legacy SCNetworkReachability // backend at runtime. Newer targets can compile directly to the modern path. #ifndef TARGET_OS_IOS -#define TARGET_OS_IOS TARGET_OS_IPHONE -#endif -#ifndef TARGET_OS_MACCATALYST -#define TARGET_OS_MACCATALYST 0 -#endif -#ifndef TARGET_OS_TV -#define TARGET_OS_TV 0 -#endif -#ifndef TARGET_OS_WATCH -#define TARGET_OS_WATCH 0 -#endif -#ifndef __TVOS_12_0 -#define __TVOS_12_0 120000 -#endif -#ifndef __WATCHOS_5_0 -#define __WATCHOS_5_0 50000 +#define TARGET_OS_IOS 0 #endif -#if TARGET_OS_IOS || TARGET_OS_MACCATALYST +#if TARGET_OS_IOS #define ODW_LEGACY_REACHABILITY_REQUIRED (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0) -#elif TARGET_OS_TV -#define ODW_LEGACY_REACHABILITY_REQUIRED (__TV_OS_VERSION_MIN_REQUIRED < __TVOS_12_0) -#elif TARGET_OS_WATCH -#define ODW_LEGACY_REACHABILITY_REQUIRED (__WATCH_OS_VERSION_MIN_REQUIRED < __WATCHOS_5_0) #elif TARGET_OS_OSX #define ODW_LEGACY_REACHABILITY_REQUIRED (__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_14) #else #define ODW_LEGACY_REACHABILITY_REQUIRED 0 #endif -#define ODW_REACHABILITY_HAS_WWAN (TARGET_OS_IOS || TARGET_OS_MACCATALYST) +#define ODW_REACHABILITY_HAS_WWAN TARGET_OS_IOS typedef NS_ENUM(NSInteger, ODWNetworkStatus) { // Apple NetworkStatus Compatible Names. diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index aa1aa1b4c..0940cef46 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -72,10 +72,10 @@ -(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; -(BOOL)getReachabilityFlags:(SCNetworkReachabilityFlags *)flags; -(BOOL)startLegacyNotifier; -(void)stopLegacyNotifier; --(BOOL)ensureModernPathMonitor API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)); --(BOOL)awaitModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)); --(void)handleModernPathUpdate:(nw_path_t)path API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)); --(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)); +-(BOOL)ensureModernPathMonitor API_AVAILABLE(macos(10.14), ios(12.0)); +-(BOOL)awaitModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); +-(void)handleModernPathUpdate:(nw_path_t)path API_AVAILABLE(macos(10.14), ios(12.0)); +-(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0)); @end @@ -98,7 +98,7 @@ -(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; } -static BOOL ODWModernPathIsReachable(nw_path_status_t status) API_AVAILABLE(macos(10.14), ios(12.0), tvos(12.0), watchos(5.0)) +static BOOL ODWModernPathIsReachable(nw_path_status_t status) API_AVAILABLE(macos(10.14), ios(12.0)) { return status == nw_path_status_satisfied || status == nw_path_status_satisfiable; } @@ -269,7 +269,7 @@ +(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:( +(ODWReachability *)reachabilityForInternetConnection { #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { return [[self alloc] init]; } @@ -288,7 +288,7 @@ +(ODWReachability *)reachabilityForInternetConnection +(ODWReachability*)reachabilityForLocalWiFi { #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { ODWReachability *reachability = [[self alloc] init]; reachability.monitorLocalWiFiOnly = YES; @@ -524,7 +524,7 @@ -(BOOL)startNotifier } #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { #endif // Use NWPathMonitor for macOS 10.14 or higher. @@ -599,7 +599,7 @@ -(void)stopNotifier } #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { #endif // Use NWPathMonitor for macOS 10.14 or higher. @@ -698,7 +698,7 @@ -(BOOL)isReachable } #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { #endif return [self awaitModernPathSnapshot] && ODWModernPathIsReachable(self.currentPathStatus); @@ -722,7 +722,7 @@ -(BOOL)isReachableViaWWAN } #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { #endif return [self awaitModernPathSnapshot] && @@ -758,7 +758,7 @@ -(BOOL)isReachableViaWiFi } #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { #endif if (![self awaitModernPathSnapshot] || !ODWModernPathIsReachable(self.currentPathStatus)) @@ -800,7 +800,7 @@ -(BOOL)connectionRequired } #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { #endif return [self awaitModernPathSnapshot] && @@ -878,7 +878,7 @@ -(SCNetworkReachabilityFlags)reachabilityFlags } #if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, tvOS 12.0, watchOS 5.0, *)) + if (@available(macOS 10.14, iOS 12.0, *)) { #endif if (![self awaitModernPathSnapshot]) From 511320c4a489a99d37e11c2f14ce88894fc638c3 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 11 May 2026 15:14:00 -0500 Subject: [PATCH 12/24] Validate +reachabilityWithAddress: inputs Reject the IPv4 unspecified wildcard (INADDR_ANY / 0.0.0.0), the IPv6 unspecified wildcard (in6addr_any / ::), and any sa_family that is not AF_INET or AF_INET6 from the public +reachabilityWithAddress: entry point. These are not routable host addresses, so creating an SC ref for them produces an ambiguous reachability probe rather than a per-host result. The legacy +reachabilityForInternetConnection fallback (only reached on deployment targets older than macOS 10.14 / iOS 12) still needs the 'probe any internet' 0.0.0.0 sentinel, so it now creates the SC ref inline and bypasses the public validator. Also document why hostname-based reachability still routes through SCNetworkReachabilityCreateWithName: NWPathMonitor has no public hostname-targeted monitoring API. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 40 +++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 0940cef46..ec50fafa7 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -158,6 +158,11 @@ +(instancetype)reachabilityWithHostname:(NSString*)hostname url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", reachabilityHost]]; } + // NWPathMonitor has no public hostname-targeted API: it monitors the system + // network path, not per-host reachability. Hostname-based reachability still + // routes through SCNetworkReachabilityCreateWithName, with the deprecated-API + // warning locally suppressed. The modern path-monitor backend is used by the + // hostname-agnostic isReachable* methods below. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [reachabilityHost UTF8String]); @@ -199,6 +204,15 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress { ipv4Address->sin_len = sizeof(*ipv4Address); } + // Reject the unspecified IPv4 wildcard (INADDR_ANY / 0.0.0.0). It is not a + // routable host address; the SDK only uses it internally as the legacy + // "internet anywhere" SC probe, which goes through a private path that + // bypasses this validator. + if (ipv4Address->sin_addr.s_addr == htonl(INADDR_ANY)) + { + NSLog(@"Invalid address: IPv4 unspecified address (0.0.0.0) is not a valid host"); + return nil; + } address = (struct sockaddr *)ipv4Address; if (inet_ntop(AF_INET, &ipv4Address->sin_addr, addressString, sizeof(addressString)) != NULL) { @@ -214,12 +228,23 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress { ipv6Address->sin6_len = sizeof(*ipv6Address); } + // Reject the unspecified IPv6 wildcard (in6addr_any / ::), same reasoning as IPv4. + if (memcmp(&ipv6Address->sin6_addr, &in6addr_any, sizeof(struct in6_addr)) == 0) + { + NSLog(@"Invalid address: IPv6 unspecified address (::) is not a valid host"); + return nil; + } address = (struct sockaddr *)ipv6Address; if (inet_ntop(AF_INET6, &ipv6Address->sin6_addr, addressString, sizeof(addressString)) != NULL) { url = [NSURL URLWithString:[NSString stringWithFormat:@"https://[%s]", addressString]]; } } + else + { + NSLog(@"Invalid address: unsupported sa_family %d (expected AF_INET or AF_INET6)", address->sa_family); + return nil; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" @@ -277,12 +302,25 @@ +(ODWReachability *)reachabilityForInternetConnection return [[self alloc] init]; #endif + // Legacy SC fallback. Apple's reference Reachability uses the zero IPv4 + // address (INADDR_ANY) here as a "probe any internet" sentinel — the public + // +reachabilityWithAddress: now rejects that wildcard, so create the SC ref + // directly and bypass the validator. struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); zeroAddress.sin_family = AF_INET; - return [self reachabilityWithAddress:&zeroAddress]; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress( + kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress); +#pragma clang diagnostic pop + if (ref) + { + return [[self alloc] initWithReachabilityRef:ref]; + } + return nil; } +(ODWReachability*)reachabilityForLocalWiFi From f2dab794214e5ee3a4a89dc1451a654d84a63fa9 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Fri, 22 May 2026 18:27:47 -0500 Subject: [PATCH 13/24] Address PR #1431 review comments - Unconditionally set sockaddr_in::sin_len / sockaddr_in6::sin6_len in +reachabilityWithAddress: after copying the caller-supplied address. Callers (including the unit tests in ODWReachabilityTests.mm) don't reliably initialize the BSD length byte, so the previous "fix it only when zero" pattern let garbage values through and caused SCNetworkReachabilityCreateWithAddress to fail on otherwise-valid IPv4/IPv6 inputs. - Restructure +reachabilityForInternetConnection and +reachabilityForLocalWiFi so the legacy SCNetworkReachability fallback code is wrapped in #if ODW_LEGACY_REACHABILITY_REQUIRED instead of living below a modern early-return as unreachable code. This actually compiles out the deprecated SC creation APIs on modern targets, which is the stated goal of the PR, rather than relying on dead-code + pragma suppression. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 42 +++++++++++----------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index ec50fafa7..fa8f960e8 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -200,10 +200,10 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress char addressString[INET_ADDRSTRLEN] = { 0 }; struct sockaddr_in *ipv4Address = (struct sockaddr_in *)&addressStorage; *ipv4Address = *(struct sockaddr_in *)hostAddress; - if (ipv4Address->sin_len == 0) - { - ipv4Address->sin_len = sizeof(*ipv4Address); - } + // BSD sockaddr_in callers (including the unit tests) don't reliably + // initialize sin_len; setting it ourselves avoids passing a garbage + // length to SCNetworkReachabilityCreateWithAddress. + ipv4Address->sin_len = sizeof(*ipv4Address); // Reject the unspecified IPv4 wildcard (INADDR_ANY / 0.0.0.0). It is not a // routable host address; the SDK only uses it internally as the legacy // "internet anywhere" SC probe, which goes through a private path that @@ -224,10 +224,10 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress char addressString[INET6_ADDRSTRLEN] = { 0 }; struct sockaddr_in6 *ipv6Address = (struct sockaddr_in6 *)&addressStorage; *ipv6Address = *(struct sockaddr_in6 *)hostAddress; - if (ipv6Address->sin6_len == 0) - { - ipv6Address->sin6_len = sizeof(*ipv6Address); - } + // BSD sockaddr_in6 callers don't reliably initialize sin6_len; setting + // it ourselves avoids passing a garbage length to + // SCNetworkReachabilityCreateWithAddress. + ipv6Address->sin6_len = sizeof(*ipv6Address); // Reject the unspecified IPv6 wildcard (in6addr_any / ::), same reasoning as IPv4. if (memcmp(&ipv6Address->sin6_addr, &in6addr_any, sizeof(struct in6_addr)) == 0) { @@ -293,19 +293,17 @@ +(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:( +(ODWReachability *)reachabilityForInternetConnection { -#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { return [[self alloc] init]; } -#else - return [[self alloc] init]; -#endif - // Legacy SC fallback. Apple's reference Reachability uses the zero IPv4 - // address (INADDR_ANY) here as a "probe any internet" sentinel — the public - // +reachabilityWithAddress: now rejects that wildcard, so create the SC ref - // directly and bypass the validator. +#if ODW_LEGACY_REACHABILITY_REQUIRED + // Legacy SC fallback for deployment targets older than macOS 10.14 / iOS 12. + // Apple's reference Reachability uses the zero IPv4 address (INADDR_ANY) here + // as a "probe any internet" sentinel — the public +reachabilityWithAddress: + // now rejects that wildcard, so create the SC ref directly and bypass the + // validator. struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); @@ -320,24 +318,21 @@ +(ODWReachability *)reachabilityForInternetConnection { return [[self alloc] initWithReachabilityRef:ref]; } +#endif return nil; } +(ODWReachability*)reachabilityForLocalWiFi { -#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { ODWReachability *reachability = [[self alloc] init]; reachability.monitorLocalWiFiOnly = YES; return reachability; } -#else - ODWReachability *reachability = [[self alloc] init]; - reachability.monitorLocalWiFiOnly = YES; - return reachability; -#endif +#if ODW_LEGACY_REACHABILITY_REQUIRED + // Legacy SC fallback for deployment targets older than macOS 10.14 / iOS 12. struct sockaddr_in localWifiAddress; bzero(&localWifiAddress, sizeof(localWifiAddress)); localWifiAddress.sin_len = sizeof(localWifiAddress); @@ -346,6 +341,9 @@ +(ODWReachability*)reachabilityForLocalWiFi localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); return [self reachabilityWithAddress:&localWifiAddress]; +#else + return nil; +#endif } From 1ff9981fbb6a30d4cceb43a537e2c0d2bc1fe02c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sat, 23 May 2026 22:03:22 -0500 Subject: [PATCH 14/24] Remove unused checkNetworkReachability helper This URLSession-based method was the early URLSession reachability probe and is no longer referenced by anything after migrating the public reachability surface to NWPathMonitor / the legacy SCNetworkReachability notifier. Drop the stale definition so the modern Apple path doesn't carry dead code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 24 ---------------------- 1 file changed, 24 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index fa8f960e8..10f39c961 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -967,30 +967,6 @@ -(NSString*)currentReachabilityFlags return reachabilityFlags([self reachabilityFlags]); } -- (SCNetworkReachabilityFlags)checkNetworkReachability:(BOOL)checkData -{ - __block BOOL connection = NO; - dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); - - NSURLSession *session = [NSURLSession sharedSession]; - NSURLSessionDataTask *task = [session dataTaskWithURL:[self url] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { - if (error == nil && !checkData) - { - connection = YES; - } - else if (error == nil && checkData && data != nil) - { - connection = YES; - } - dispatch_semaphore_signal(semaphore); - }]; - - [task resume]; - dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); - - return connection; -} - #pragma mark - Callback function calls this method -(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags From 0789ca881eaad74ee08d457ce235912613b5a8db Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Sun, 24 May 2026 01:27:02 -0500 Subject: [PATCH 15/24] Document hostname-constructor semantic change on modern Apple targets Add a header-level docstring explaining that on modern targets (iOS 12+ / macOS 10.14+), +reachabilityWithHostname: and +reachabilityWithAddress: no longer issue an NSURLSession HTTPS probe to the host's URL when -isReachable / -isReachableViaWiFi are queried. Instead they return SCNetworkReachabilityGetFlags state on the SC ref the constructor created. This is the same OS-level reachability semantics the legacy code path has always used, but it is a behavior change compared to the pre-PR modern path that performed a real HTTPS round-trip. Callers that need authoritative per-endpoint reachability should probe the endpoint themselves rather than relying on -isReachable on these instances. Document that the SDK's own production network detection (NetworkInformationImpl) uses +reachabilityForInternetConnection on legacy targets and nw_path_monitor_create() directly on modern targets, so the SDK's own behavior is unaffected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/third_party/Reachability/ODWReachability.h b/third_party/Reachability/ODWReachability.h index 190d4b9d8..5d232be59 100644 --- a/third_party/Reachability/ODWReachability.h +++ b/third_party/Reachability/ODWReachability.h @@ -89,6 +89,28 @@ typedef void (^NetworkUnreachable)(ODWReachability * reachability); +(ODWReachability*)reachabilityForLocalWiFi; +(void)setTimeoutDurationInSeconds:(int)timeoutDuration; +// ------------------------------------------------------------------------- +// Behavior note for hostname / address constructors (modern Apple targets): +// +// Prior to the NWPathMonitor migration, instances created via +// +reachabilityWithHostname: or +reachabilityWithAddress: would, on +// iOS 12+ / macOS 10.14+, internally issue an NSURLSession HTTPS GET to +// the host's URL whenever -isReachable / -isReachableViaWiFi was queried, +// yielding a true per-endpoint reachability answer (DNS + TLS round-trip). +// +// After the migration, those queries return OS-level reachability for the +// hostname/address via SCNetworkReachabilityGetFlags on the SC ref the +// constructor created. That still involves DNS via the SC stack but is +// not the same as actually probing the endpoint with HTTPS. Callers that +// require an authoritative "can I reach this exact endpoint over HTTPS?" +// answer should perform that probe themselves rather than relying on +// these per-host instances. The SDK's own production network detection +// (NetworkInformationImpl) uses +reachabilityForInternetConnection on +// legacy targets and nw_path_monitor_create() directly on modern targets; +// neither path was ever per-endpoint, so this change does not affect the +// SDK's own behavior. +// ------------------------------------------------------------------------- + -(ODWReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; -(BOOL)startNotifier; From 46002f43d20d012acefd8ad0e84f2c3888286dbe Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 25 May 2026 12:25:45 -0500 Subject: [PATCH 16/24] Simplify ODWReachability ARC-only code Drop manual-reference-counting fallback code from the new NWPathMonitor context now that the Apple builds compile this file with ARC. Files changed: - third_party/Reachability/ODWReachability.m Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- third_party/Reachability/ODWReachability.m | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 10f39c961..7e102793c 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -41,11 +41,7 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF @interface ODWReachabilityMonitorContext : NSObject -#if __has_feature(objc_arc) @property (nonatomic, weak) ODWReachability *owner; -#else -@property (nonatomic, unsafe_unretained) ODWReachability *owner; -#endif @end @@ -400,9 +396,6 @@ -(BOOL)ensureModernPathMonitor ODWReachabilityMonitorContext *context = [[ODWReachabilityMonitorContext alloc] init]; context.owner = self; self.pathMonitorContext = context; -#if !__has_feature(objc_arc) - [context release]; -#endif nw_path_monitor_set_queue(self.pathMonitor, self.reachabilitySerialQueue); nw_path_monitor_set_update_handler(self.pathMonitor, ^(nw_path_t path) { @@ -521,11 +514,6 @@ +(void)setTimeoutDurationInSeconds:(int)timeoutDuration } } -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" // Not fixing third_party components. -#endif - -(void)dealloc { [self stopNotifier]; @@ -541,10 +529,6 @@ -(void)dealloc self.reachabilitySerialQueue = nil; } -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - #pragma mark - Notifier Methods // Notifier From 7d468b0df972b181b46ecd7e81bfe823c810c48c Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 25 May 2026 13:47:31 -0500 Subject: [PATCH 17/24] Address Apple reachability review comments Synchronize NWPathMonitor snapshot reads, unblock pending snapshot waits when stopping the monitor, and guard against monitor creation failure in the SDK network detector. Files changed: - third_party/Reachability/ODWReachability.m - lib/pal/posix/NetworkInformationImpl.mm Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/pal/posix/NetworkInformationImpl.mm | 16 ++- third_party/Reachability/ODWReachability.m | 121 +++++++++++++++++---- 2 files changed, 111 insertions(+), 26 deletions(-) diff --git a/lib/pal/posix/NetworkInformationImpl.mm b/lib/pal/posix/NetworkInformationImpl.mm index 943c22667..f76eca8d4 100644 --- a/lib/pal/posix/NetworkInformationImpl.mm +++ b/lib/pal/posix/NetworkInformationImpl.mm @@ -95,7 +95,10 @@ virtual NetworkCost GetNetworkCost() { if (m_isNetDetectEnabled) { - nw_path_monitor_cancel(m_monitor); + if (m_monitor != nil) + { + nw_path_monitor_cancel(m_monitor); + } } } else @@ -109,7 +112,10 @@ virtual NetworkCost GetNetworkCost() #else if (m_isNetDetectEnabled) { - nw_path_monitor_cancel(m_monitor); + if (m_monitor != nil) + { + nw_path_monitor_cancel(m_monitor); + } } #endif } @@ -119,6 +125,12 @@ virtual NetworkCost GetNetworkCost() auto weak_this = std::weak_ptr(shared_from_this()); m_monitor = nw_path_monitor_create(); + if (m_monitor == nil) + { + LOG_WARN("Unable to create NWPathMonitor."); + return; + } + nw_path_monitor_set_queue(m_monitor, dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)); nw_path_monitor_set_update_handler(m_monitor, ^(nw_path_t path) { diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 7e102793c..40b64d71e 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -37,6 +37,14 @@ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF NSString *const kNetworkReachabilityChangedNotification = @"NetworkReachabilityChangedNotification"; static char ODWReachabilityQueueKey; +typedef struct +{ + nw_path_status_t status; + BOOL usesWiFi; + BOOL usesWWAN; + BOOL hasObservedPath; +} ODWModernPathSnapshot; + @class ODWReachability; @interface ODWReachabilityMonitorContext : NSObject @@ -70,6 +78,8 @@ -(BOOL)startLegacyNotifier; -(void)stopLegacyNotifier; -(BOOL)ensureModernPathMonitor API_AVAILABLE(macos(10.14), ios(12.0)); -(BOOL)awaitModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); +-(ODWModernPathSnapshot)currentModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); +-(void)resetModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); -(void)handleModernPathUpdate:(nw_path_t)path API_AVAILABLE(macos(10.14), ios(12.0)); -(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0)); @@ -372,6 +382,47 @@ -(ODWReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref return self; } +-(ODWModernPathSnapshot)currentModernPathSnapshot +{ + __block ODWModernPathSnapshot snapshot = { nw_path_status_invalid, NO, NO, NO }; + void (^copySnapshot)(void) = ^{ + snapshot.status = self.currentPathStatus; + snapshot.usesWiFi = self.currentPathUsesWiFi; + snapshot.usesWWAN = self.currentPathUsesWWAN; + snapshot.hasObservedPath = self.hasObservedPath; + }; + + if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) + { + copySnapshot(); + } + else + { + dispatch_sync(self.reachabilitySerialQueue, copySnapshot); + } + + return snapshot; +} + +-(void)resetModernPathSnapshot +{ + void (^resetSnapshot)(void) = ^{ + self.hasObservedPath = NO; + self.currentPathStatus = nw_path_status_invalid; + self.currentPathUsesWiFi = NO; + self.currentPathUsesWWAN = NO; + }; + + if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) + { + resetSnapshot(); + } + else + { + dispatch_sync(self.reachabilitySerialQueue, resetSnapshot); + } +} + -(BOOL)ensureModernPathMonitor { if (self.pathMonitor != nil) @@ -379,10 +430,7 @@ -(BOOL)ensureModernPathMonitor return YES; } - self.hasObservedPath = NO; - self.currentPathStatus = nw_path_status_invalid; - self.currentPathUsesWiFi = NO; - self.currentPathUsesWWAN = NO; + [self resetModernPathSnapshot]; self.initialPathSemaphore = dispatch_semaphore_create(0); self.pathMonitor = self.monitorLocalWiFiOnly ? nw_path_monitor_create_with_type(nw_interface_type_wifi) @@ -419,7 +467,7 @@ -(BOOL)awaitModernPathSnapshot return NO; } - if (self.hasObservedPath) + if ([self currentModernPathSnapshot].hasObservedPath) { return YES; } @@ -449,7 +497,7 @@ -(BOOL)awaitModernPathSnapshot long waitResult = dispatch_semaphore_wait( semaphore, dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); - return waitResult == 0 && self.hasObservedPath; + return waitResult == 0 && [self currentModernPathSnapshot].hasObservedPath; } -(void)handleModernPathUpdate:(nw_path_t)path @@ -484,7 +532,8 @@ -(void)handleModernPathUpdate:(nw_path_t)path -(void)notifyModernPathChange { - if (ODWModernPathIsReachable(self.currentPathStatus)) + ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; + if (ODWModernPathIsReachable(snapshot.status)) { if (self.reachableBlock) { @@ -624,6 +673,7 @@ -(void)stopNotifier #endif // Use NWPathMonitor for macOS 10.14 or higher. self.reachabilityObject = nil; + dispatch_semaphore_t semaphore = self.initialPathSemaphore; if (self.pathMonitor != nil) { self.pathMonitorContext.owner = nil; @@ -631,11 +681,12 @@ -(void)stopNotifier self.pathMonitor = nil; } self.pathMonitorContext = nil; + [self resetModernPathSnapshot]; + if (semaphore != nil) + { + dispatch_semaphore_signal(semaphore); + } self.initialPathSemaphore = nil; - self.hasObservedPath = NO; - self.currentPathStatus = nw_path_status_invalid; - self.currentPathUsesWiFi = NO; - self.currentPathUsesWWAN = NO; #if ODW_LEGACY_REACHABILITY_REQUIRED return; } @@ -721,7 +772,13 @@ -(BOOL)isReachable if (@available(macOS 10.14, iOS 12.0, *)) { #endif - return [self awaitModernPathSnapshot] && ODWModernPathIsReachable(self.currentPathStatus); + if (![self awaitModernPathSnapshot]) + { + return NO; + } + + ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; + return ODWModernPathIsReachable(snapshot.status); #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -745,9 +802,13 @@ -(BOOL)isReachableViaWWAN if (@available(macOS 10.14, iOS 12.0, *)) { #endif - return [self awaitModernPathSnapshot] && - ODWModernPathIsReachable(self.currentPathStatus) && - self.currentPathUsesWWAN; + if (![self awaitModernPathSnapshot]) + { + return NO; + } + + ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; + return ODWModernPathIsReachable(snapshot.status) && snapshot.usesWWAN; #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -781,19 +842,25 @@ -(BOOL)isReachableViaWiFi if (@available(macOS 10.14, iOS 12.0, *)) { #endif - if (![self awaitModernPathSnapshot] || !ODWModernPathIsReachable(self.currentPathStatus)) + if (![self awaitModernPathSnapshot]) + { + return NO; + } + + ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; + if (!ODWModernPathIsReachable(snapshot.status)) { return NO; } #if ODW_REACHABILITY_HAS_WWAN if (self.monitorLocalWiFiOnly) { - return self.currentPathUsesWiFi; + return snapshot.usesWiFi; } - return !self.currentPathUsesWWAN; + return !snapshot.usesWWAN; #else - return self.monitorLocalWiFiOnly ? self.currentPathUsesWiFi : YES; + return self.monitorLocalWiFiOnly ? snapshot.usesWiFi : YES; #endif #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -823,8 +890,13 @@ -(BOOL)connectionRequired if (@available(macOS 10.14, iOS 12.0, *)) { #endif - return [self awaitModernPathSnapshot] && - self.currentPathStatus == nw_path_status_satisfiable; + if (![self awaitModernPathSnapshot]) + { + return NO; + } + + ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; + return snapshot.status == nw_path_status_satisfiable; #if ODW_LEGACY_REACHABILITY_REQUIRED } @@ -906,17 +978,18 @@ -(SCNetworkReachabilityFlags)reachabilityFlags return 0; } + ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; SCNetworkReachabilityFlags flags = 0; - if (ODWModernPathIsReachable(self.currentPathStatus)) + if (ODWModernPathIsReachable(snapshot.status)) { flags |= kSCNetworkReachabilityFlagsReachable; } - if (self.currentPathStatus == nw_path_status_satisfiable) + if (snapshot.status == nw_path_status_satisfiable) { flags |= kSCNetworkReachabilityFlagsConnectionRequired; } #if ODW_REACHABILITY_HAS_WWAN - if (self.currentPathUsesWWAN) + if (snapshot.usesWWAN) { flags |= kSCNetworkReachabilityFlagsIsWWAN; } From c7a186425236fbbf4bfd04370c6694b661824bfe Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 25 May 2026 16:37:26 -0500 Subject: [PATCH 18/24] Fix Apple reachability monitor races Serialize NWPathMonitor initialization and use a dispatch group so every caller waiting for the first path snapshot is released when the snapshot arrives or monitoring stops. Files changed: - third_party/Reachability/ODWReachability.m - lib/pal/posix/NetworkInformationImpl.mm Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/pal/posix/NetworkInformationImpl.mm | 4 +- third_party/Reachability/ODWReachability.m | 137 +++++++++++++-------- 2 files changed, 90 insertions(+), 51 deletions(-) diff --git a/lib/pal/posix/NetworkInformationImpl.mm b/lib/pal/posix/NetworkInformationImpl.mm index f76eca8d4..4bf4c4692 100644 --- a/lib/pal/posix/NetworkInformationImpl.mm +++ b/lib/pal/posix/NetworkInformationImpl.mm @@ -59,8 +59,8 @@ virtual NetworkCost GetNetworkCost() /// /// Setup initial network information and start net monitor if requested. /// This cannot be put in constructor because we need to use shared_from_this. - /// - void SetupNetDetect(); + /// + void SetupNetDetect(); private: void SetupModernNetDetect() API_AVAILABLE(macos(10.14), ios(12.0)); diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 40b64d71e..6a02ca0b2 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -64,7 +64,7 @@ @interface ODWReachability () @property (nonatomic, strong) id reachabilityObject; @property (nonatomic, strong) nw_path_monitor_t pathMonitor; @property (nonatomic, strong) ODWReachabilityMonitorContext *pathMonitorContext; -@property (nonatomic, strong) dispatch_semaphore_t initialPathSemaphore; +@property (nonatomic, strong) dispatch_group_t initialPathGroup; @property (nonatomic, assign) nw_path_status_t currentPathStatus; @property (nonatomic, assign) BOOL currentPathUsesWiFi; @property (nonatomic, assign) BOOL currentPathUsesWWAN; @@ -425,39 +425,56 @@ -(void)resetModernPathSnapshot -(BOOL)ensureModernPathMonitor { - if (self.pathMonitor != nil) - { - return YES; - } - - [self resetModernPathSnapshot]; - self.initialPathSemaphore = dispatch_semaphore_create(0); - self.pathMonitor = self.monitorLocalWiFiOnly - ? nw_path_monitor_create_with_type(nw_interface_type_wifi) - : nw_path_monitor_create(); - - if (self.pathMonitor == nil) - { - return NO; - } + __block BOOL ensured = NO; + void (^ensureMonitor)(void) = ^{ + if (self.pathMonitor != nil) + { + ensured = YES; + return; + } - ODWReachabilityMonitorContext *context = [[ODWReachabilityMonitorContext alloc] init]; - context.owner = self; - self.pathMonitorContext = context; + nw_path_monitor_t monitor = self.monitorLocalWiFiOnly + ? nw_path_monitor_create_with_type(nw_interface_type_wifi) + : nw_path_monitor_create(); - nw_path_monitor_set_queue(self.pathMonitor, self.reachabilitySerialQueue); - nw_path_monitor_set_update_handler(self.pathMonitor, ^(nw_path_t path) { - ODWReachability *owner = context.owner; - if (owner == nil) + if (monitor == nil) { return; } - [owner handleModernPathUpdate:path]; - }); - nw_path_monitor_start(self.pathMonitor); + [self resetModernPathSnapshot]; + self.initialPathGroup = dispatch_group_create(); + dispatch_group_enter(self.initialPathGroup); + self.pathMonitor = monitor; + + ODWReachabilityMonitorContext *context = [[ODWReachabilityMonitorContext alloc] init]; + context.owner = self; + self.pathMonitorContext = context; + + nw_path_monitor_set_queue(self.pathMonitor, self.reachabilitySerialQueue); + nw_path_monitor_set_update_handler(self.pathMonitor, ^(nw_path_t path) { + ODWReachability *owner = context.owner; + if (owner == nil) + { + return; + } + + [owner handleModernPathUpdate:path]; + }); + nw_path_monitor_start(self.pathMonitor); + ensured = YES; + }; + + if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) + { + ensureMonitor(); + } + else + { + dispatch_sync(self.reachabilitySerialQueue, ensureMonitor); + } - return YES; + return ensured; } -(BOOL)awaitModernPathSnapshot @@ -472,11 +489,23 @@ -(BOOL)awaitModernPathSnapshot return YES; } - // Capture the semaphore into a local so a concurrent -stopNotifier on + // Capture the group into a local so a concurrent -stopNotifier on // another thread cannot release the property between the nil-check and // the wait below. - dispatch_semaphore_t semaphore = self.initialPathSemaphore; - if (semaphore == nil) + __block dispatch_group_t group = nil; + void (^copyInitialPathGroup)(void) = ^{ + group = self.initialPathGroup; + }; + if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) + { + copyInitialPathGroup(); + } + else + { + dispatch_sync(self.reachabilitySerialQueue, copyInitialPathGroup); + } + + if (group == nil) { return NO; } @@ -494,8 +523,8 @@ -(BOOL)awaitModernPathSnapshot return NO; } - long waitResult = dispatch_semaphore_wait( - semaphore, + long waitResult = dispatch_group_wait( + group, dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); return waitResult == 0 && [self currentModernPathSnapshot].hasObservedPath; } @@ -514,13 +543,10 @@ -(void)handleModernPathUpdate:(nw_path_t)path self.hasObservedPath = YES; if (firstPath) { - // Capture the semaphore into a local so a concurrent -stopNotifier - // on another thread cannot release the property between the - // nil-check and the signal below. - dispatch_semaphore_t semaphore = self.initialPathSemaphore; - if (semaphore != nil) + dispatch_group_t group = self.initialPathGroup; + if (group != nil) { - dispatch_semaphore_signal(semaphore); + dispatch_group_leave(group); } } @@ -672,21 +698,34 @@ -(void)stopNotifier { #endif // Use NWPathMonitor for macOS 10.14 or higher. - self.reachabilityObject = nil; - dispatch_semaphore_t semaphore = self.initialPathSemaphore; - if (self.pathMonitor != nil) + void (^stopMonitor)(void) = ^{ + dispatch_group_t group = self.initialPathGroup; + BOOL shouldLeaveGroup = group != nil && !self.hasObservedPath; + + self.reachabilityObject = nil; + if (self.pathMonitor != nil) + { + self.pathMonitorContext.owner = nil; + nw_path_monitor_cancel(self.pathMonitor); + self.pathMonitor = nil; + } + self.pathMonitorContext = nil; + [self resetModernPathSnapshot]; + if (shouldLeaveGroup) + { + dispatch_group_leave(group); + } + self.initialPathGroup = nil; + }; + + if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) { - self.pathMonitorContext.owner = nil; - nw_path_monitor_cancel(self.pathMonitor); - self.pathMonitor = nil; + stopMonitor(); } - self.pathMonitorContext = nil; - [self resetModernPathSnapshot]; - if (semaphore != nil) + else { - dispatch_semaphore_signal(semaphore); + dispatch_sync(self.reachabilitySerialQueue, stopMonitor); } - self.initialPathSemaphore = nil; #if ODW_LEGACY_REACHABILITY_REQUIRED return; } From 566fda214c55aa2dd2c9872d9354dc509e49cf66 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 25 May 2026 16:52:24 -0500 Subject: [PATCH 19/24] Fix Reachability hostname parsing Copilot: host:port inputs were parsed as a scheme with no host. Parse by prepending https:// before NSURLComponents when no explicit scheme separator is present. Copilot: invalid.hostname is syntactically valid under SCNetworkReachabilityCreateWithName. Update tests to distinguish malformed host URLs from unresolved hostnames. Files changed: - third_party/Reachability/ODWReachability.m - tests/unittests/obj-c/ODWReachabilityTests.mm Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/unittests/obj-c/ODWReachabilityTests.mm | 27 ++++++++++++++----- third_party/Reachability/ODWReachability.m | 23 +++++++--------- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/tests/unittests/obj-c/ODWReachabilityTests.mm b/tests/unittests/obj-c/ODWReachabilityTests.mm index cc8a7c24e..e74dd8588 100644 --- a/tests/unittests/obj-c/ODWReachabilityTests.mm +++ b/tests/unittests/obj-c/ODWReachabilityTests.mm @@ -36,16 +36,29 @@ - (void)testReachabilityWithHostname } - (void)testReachabilityWithInvalidHostname +{ + NSString *hostname = @"https://"; + ODWReachability *reachability = [ODWReachability reachabilityWithHostname:hostname]; + + XCTAssertNil(reachability); +} + +- (void)testReachabilityWithUnresolvedHostname { NSString *hostname = @"invalid.hostname"; ODWReachability *reachability = [ODWReachability reachabilityWithHostname:hostname]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Reachability check"]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - XCTAssertNil(reachability); - [expectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10.0 handler:nil]; + + XCTAssertNotNil(reachability); + XCTAssertEqualObjects(reachability.url.absoluteString, @"https://invalid.hostname"); +} + +- (void)testReachabilityWithHostAndPort +{ + NSString *hostname = @"example.com:8080"; + ODWReachability *reachability = [ODWReachability reachabilityWithHostname:hostname]; + + XCTAssertNotNil(reachability); + XCTAssertEqualObjects(reachability.url.absoluteString, @"https://example.com:8080"); } - (void)testReachabilityWithAddress diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 6a02ca0b2..15d03594e 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -144,24 +144,21 @@ +(instancetype)reachabilityWithHostname:(NSString*)hostname return nil; } - NSString *reachabilityHost = hostname; - NSURL *url = nil; - NSURLComponents *components = [NSURLComponents componentsWithString:hostname]; - if ([components.scheme length] > 0) + BOOL hasExplicitScheme = [hostname rangeOfString:@"://"].location != NSNotFound; + NSString *urlString = hasExplicitScheme ? hostname : [NSString stringWithFormat:@"https://%@", hostname]; + NSURLComponents *components = [NSURLComponents componentsWithString:urlString]; + if (components == nil || [components.host length] == 0) { - if ([components.host length] == 0) - { - NSLog(@"Invalid hostname '%@': URL has no host", hostname); - return nil; - } - - reachabilityHost = components.host; - url = components.URL; + NSLog(@"Invalid hostname '%@': URL has no host", hostname); + return nil; } + NSString *reachabilityHost = components.host; + NSURL *url = components.URL; if (url == nil) { - url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", reachabilityHost]]; + NSLog(@"Invalid hostname '%@': URL could not be constructed", hostname); + return nil; } // NWPathMonitor has no public hostname-targeted API: it monitors the system From 791f1dc0af94cac426e3732442011bc0f6b47321 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 25 May 2026 17:11:27 -0500 Subject: [PATCH 20/24] Clarify Reachability ARC build behavior Copilot: ODWReachability.m could look non-ARC under CMake because ARC was only set in CXX flags. Compile the vendored Objective-C source with -fobjc-arc explicitly. Copilot: rename the unresolved-hostname test to describe constructor acceptance rather than DNS reachability. Files changed: - lib/CMakeLists.txt - tests/unittests/obj-c/ODWReachabilityTests.mm Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/CMakeLists.txt | 3 ++- tests/unittests/obj-c/ODWReachabilityTests.mm | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 4ebe7ddb0..d08e8a620 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -173,6 +173,8 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") # TODO: this unit below needs to be deprecated and removed ../third_party/Reachability/ODWReachability.m ) + set_source_files_properties(../third_party/Reachability/ODWReachability.m + PROPERTIES COMPILE_FLAGS "-fobjc-arc") else() list(APPEND SRCS http/HttpClient_Curl.cpp @@ -342,4 +344,3 @@ message("-- Library will be installed to ${INSTALL_LIB_DIR}") # #target_link_libraries(mat PUBLIC libcurl.a libz.a libssl.a libcrypto.a "${SQLITE_LIBRARY}" "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) # #target_link_libraries(mat PUBLIC libsqlite3.a libz.a ${LIBS} "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) #endif() - diff --git a/tests/unittests/obj-c/ODWReachabilityTests.mm b/tests/unittests/obj-c/ODWReachabilityTests.mm index e74dd8588..34c03e9bc 100644 --- a/tests/unittests/obj-c/ODWReachabilityTests.mm +++ b/tests/unittests/obj-c/ODWReachabilityTests.mm @@ -43,7 +43,7 @@ - (void)testReachabilityWithInvalidHostname XCTAssertNil(reachability); } -- (void)testReachabilityWithUnresolvedHostname +- (void)testReachabilityWithUnresolvedHostnameCreatesInstance { NSString *hostname = @"invalid.hostname"; ODWReachability *reachability = [ODWReachability reachabilityWithHostname:hostname]; From 9f93d9d6a3dbb342c01946d3d7db12e806fb6c0b Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 25 May 2026 17:27:30 -0500 Subject: [PATCH 21/24] Remove blocking Reachability notifier startup Copilot: delete the dead URLSession response helper left behind after removing endpoint probes. Copilot: make modern startNotifier match legacy behavior by wiring callbacks and returning without waiting for the first NWPathMonitor snapshot. Copilot: add a start/stop notifier test that verifies startup returns promptly. Files changed: - third_party/Reachability/ODWReachability.m - tests/unittests/obj-c/ODWReachabilityTests.mm Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/unittests/obj-c/ODWReachabilityTests.mm | 12 +++++++ third_party/Reachability/ODWReachability.m | 31 ------------------- 2 files changed, 12 insertions(+), 31 deletions(-) diff --git a/tests/unittests/obj-c/ODWReachabilityTests.mm b/tests/unittests/obj-c/ODWReachabilityTests.mm index 34c03e9bc..827406290 100644 --- a/tests/unittests/obj-c/ODWReachabilityTests.mm +++ b/tests/unittests/obj-c/ODWReachabilityTests.mm @@ -118,4 +118,16 @@ - (void)testReachabilityForLocalWiFi [self waitForExpectationsWithTimeout:10.0 handler:nil]; } +- (void)testStartAndStopNotifier +{ + ODWReachability *reachability = [ODWReachability reachabilityForInternetConnection]; + XCTAssertNotNil(reachability); + + NSDate *start = [NSDate date]; + XCTAssertTrue([reachability startNotifier]); + XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:start], 1.0); + + [reachability stopNotifier]; +} + @end diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 15d03594e..7a5e5fde6 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -267,33 +267,6 @@ +(ODWReachability *)reachabilityWithAddress:(void *)hostAddress return nil; } -+(ODWReachability *)handleReachabilityResponse:(NSURLResponse *)response error:(NSError *)error url:(NSURL *)url -{ - __block ODWReachability *reachabilityInstance = nil; - - if (error == nil) { - // Handle successful reachability - NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; - if (httpResponse.statusCode == 200) - { - NSLog(@"Reachability success: %@", url); - reachabilityInstance = [[self alloc] init]; - reachabilityInstance.url = url; - } - else - { - NSLog(@"Reachability failed with status code: %ld", (long)httpResponse.statusCode); - } - return reachabilityInstance; - } - - // Handle reachability failure - NSLog(@"Reachability error: %@", error.localizedDescription); - - return nil; -} - - +(ODWReachability *)reachabilityForInternetConnection { if (@available(macOS 10.14, iOS 12.0, *)) @@ -623,10 +596,6 @@ -(BOOL)startNotifier if ([self ensureModernPathMonitor]) { self.reachabilityObject = self; - if ([self awaitModernPathSnapshot]) - { - [self notifyModernPathChange]; - } return YES; } return NO; From 3fed4dc054afa8c054a0de137e17fc9ef5db1c6a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Mon, 25 May 2026 18:16:09 -0500 Subject: [PATCH 22/24] Match repo style in reachability files Restore the access-specifier indentation in NetworkInformationImpl.mm to the 4-space convention used on main. Restore the trailing blank line at the end of lib/CMakeLists.txt accidentally dropped during the ARC compile-flags patch. Realign one stray line in ODWReachability.m reachabilityFlags() to match surrounding column alignment. Files changed: - lib/CMakeLists.txt - lib/pal/posix/NetworkInformationImpl.mm - third_party/Reachability/ODWReachability.m Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- lib/CMakeLists.txt | 1 + lib/pal/posix/NetworkInformationImpl.mm | 26 +++++++++++----------- third_party/Reachability/ODWReachability.m | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d08e8a620..a1ef3305a 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -344,3 +344,4 @@ message("-- Library will be installed to ${INSTALL_LIB_DIR}") # #target_link_libraries(mat PUBLIC libcurl.a libz.a libssl.a libcrypto.a "${SQLITE_LIBRARY}" "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) # #target_link_libraries(mat PUBLIC libsqlite3.a libz.a ${LIBS} "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) #endif() + diff --git a/lib/pal/posix/NetworkInformationImpl.mm b/lib/pal/posix/NetworkInformationImpl.mm index 4bf4c4692..561707ab5 100644 --- a/lib/pal/posix/NetworkInformationImpl.mm +++ b/lib/pal/posix/NetworkInformationImpl.mm @@ -20,7 +20,7 @@ class NetworkInformation : public NetworkInformationImpl, public std::enable_shared_from_this { - public: + public: /// /// /// @@ -62,24 +62,24 @@ virtual NetworkCost GetNetworkCost() /// void SetupNetDetect(); - private: - void SetupModernNetDetect() API_AVAILABLE(macos(10.14), ios(12.0)); + private: + void SetupModernNetDetect() API_AVAILABLE(macos(10.14), ios(12.0)); #if ODW_LEGACY_REACHABILITY_REQUIRED - void SetupLegacyNetDetect(); + void SetupLegacyNetDetect(); #endif - void UpdateType(NetworkType type) noexcept; - void UpdateCost(NetworkCost cost) noexcept; - std::string m_network_provider {}; + void UpdateType(NetworkType type) noexcept; + void UpdateCost(NetworkCost cost) noexcept; + std::string m_network_provider {}; - // iOS 12+ / macOS 10.14+ - nw_path_monitor_t m_monitor = nil; + // iOS 12+ / macOS 10.14+ + nw_path_monitor_t m_monitor = nil; #if ODW_LEGACY_REACHABILITY_REQUIRED - // Older Apple deployment targets still need the legacy fallback. - ODWReachability* m_reach = nil; - id m_notificationId = nil; + // Older Apple deployment targets still need the legacy fallback. + ODWReachability* m_reach = nil; + id m_notificationId = nil; #endif - }; + }; NetworkInformation::NetworkInformation(IRuntimeConfig& configuration) : NetworkInformationImpl(configuration) diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m index 7a5e5fde6..1c802f6e2 100644 --- a/third_party/Reachability/ODWReachability.m +++ b/third_party/Reachability/ODWReachability.m @@ -101,7 +101,7 @@ -(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0)); (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', - (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; + (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; } static BOOL ODWModernPathIsReachable(nw_path_status_t status) API_AVAILABLE(macos(10.14), ios(12.0)) From eadd84b602e450d56bd2eb8e3e1520061c0b80ea Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 26 May 2026 15:04:32 -0500 Subject: [PATCH 23/24] Bump default iOS deployment target to 12.0 Align build-ios.sh with the README support matrix (iOS 12+) so default builds compile out the legacy SCNetworkReachability code path that ODW_LEGACY_REACHABILITY_REQUIRED gates. Borrowed from #1371. Files changed: - build-ios.sh Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- build-ios.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-ios.sh b/build-ios.sh index 1fc411a7c..d316fe2fa 100755 --- a/build-ios.sh +++ b/build-ios.sh @@ -56,7 +56,7 @@ if [ "$IOS_PLAT" == "iphoneos" ] || [ "$IOS_PLAT" == "iphonesimulator" ]; then SYS_NAME="iOS" DEPLOYMENT_TARGET="$IOS_DEPLOYMENT_TARGET" if [ -z "$DEPLOYMENT_TARGET" ]; then - DEPLOYMENT_TARGET="10.0" + DEPLOYMENT_TARGET="12.0" FORCE_RESET_DEPLOYMENT_TARGET=YES fi elif [ "$IOS_PLAT" == "xros" ] || [ "$IOS_PLAT" == "xrsimulator" ]; then From 5055cc4d64e5235d9d2a358666ed9b5fdfd1e37a Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Tue, 26 May 2026 16:37:36 -0500 Subject: [PATCH 24/24] Remove internal ODWReachability helper ODWReachability is internal-only, so keep the SDK on direct NWPathMonitor instead of maintaining a compatibility wrapper around the vendored Reachability snapshot. Remove the vendored Reachability sources, tests, CMake wiring, Xcode references, and OSS manifest entries. Unsupported pre-iOS-12 / pre-macOS-10.14 runtimes now leave network state as Unknown instead of using SCNetworkReachability. Files changed: - lib/pal/posix/NetworkInformationImpl.mm - lib/CMakeLists.txt - tests/unittests/unittests-ios.xcodeproj/project.pbxproj - tests/unittests/obj-c/ODWReachabilityTests.mm - third_party/Reachability/* - docs/List-of-OSS-Components.md - third_party/cgmanifest.json Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/List-of-OSS-Components.md | 6 - lib/CMakeLists.txt | 7 +- lib/pal/posix/NetworkInformationImpl.mm | 82 +- tests/unittests/obj-c/ODWReachabilityTests.mm | 133 -- .../unittests-ios.xcodeproj/project.pbxproj | 4 - third_party/Reachability/ODWReachability.h | 137 --- third_party/Reachability/ODWReachability.m | 1067 ----------------- third_party/Reachability/README.md | 28 - third_party/cgmanifest.json | 10 - 9 files changed, 3 insertions(+), 1471 deletions(-) delete mode 100644 tests/unittests/obj-c/ODWReachabilityTests.mm delete mode 100644 third_party/Reachability/ODWReachability.h delete mode 100644 third_party/Reachability/ODWReachability.m delete mode 100644 third_party/Reachability/README.md diff --git a/docs/List-of-OSS-Components.md b/docs/List-of-OSS-Components.md index 094ea1472..4a6888876 100644 --- a/docs/List-of-OSS-Components.md +++ b/docs/List-of-OSS-Components.md @@ -38,12 +38,6 @@ Google's C++ test framework. Used only for tests and not included in products. Google's C++ benchmarking framework. Used only for tests and not included in products. -## [Tony Million Reachability Framework](https://github.com/tonymillion/Reachability) - -Reachability is a drop-in replacement for Apple's Reachability class. It is ARC-compatible, and it uses the new GCD methods to notify of network interface changes. -SDK maintains its own snapshot of the mainline `tonymillion/Reachability` [here](../third_party/Reachability). This code is not used nor included in the build of non-Apple OS. -Please note if customer product is adding dependency to this component, they should ensure it meets their product security and licensing requirements. - ## SHA-1 by Steve Reid Classic implementation of SHA-1 (Public Domain). diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a1ef3305a..a19ad89fa 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,7 +1,7 @@ # Honor visibility properties for all target types cmake_policy(SET CMP0063 NEW) -include_directories( . ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include/public ${CMAKE_CURRENT_SOURCE_DIR}/include/public ${CMAKE_CURRENT_SOURCE_DIR}/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/pal ${CMAKE_CURRENT_SOURCE_DIR}/utils ${CMAKE_CURRENT_SOURCE_DIR}/modules/exp ${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard ${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/Reachability ${CMAKE_CURRENT_SOURCE_DIR}/modules/cds ${CMAKE_CURRENT_SOURCE_DIR}/modules/signals ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer /usr/local/include ) +include_directories( . ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/include/public ${CMAKE_CURRENT_SOURCE_DIR}/include/public ${CMAKE_CURRENT_SOURCE_DIR}/include/mat ${CMAKE_CURRENT_SOURCE_DIR}/pal ${CMAKE_CURRENT_SOURCE_DIR}/utils ${CMAKE_CURRENT_SOURCE_DIR}/modules/exp ${CMAKE_CURRENT_SOURCE_DIR}/modules/dataviewer ${CMAKE_CURRENT_SOURCE_DIR}/modules/privacyguard ${CMAKE_CURRENT_SOURCE_DIR}/modules/liveeventinspector ${CMAKE_CURRENT_SOURCE_DIR}/modules/cds ${CMAKE_CURRENT_SOURCE_DIR}/modules/signals ${CMAKE_CURRENT_SOURCE_DIR}/modules/sanitizer /usr/local/include ) set(SRCS decorators/BaseDecorator.cpp packager/BondSplicer.cpp @@ -170,11 +170,7 @@ if(PAL_IMPLEMENTATION STREQUAL "CPP11") list(APPEND SRCS pal/posix/NetworkInformationImpl.mm - # TODO: this unit below needs to be deprecated and removed - ../third_party/Reachability/ODWReachability.m ) - set_source_files_properties(../third_party/Reachability/ODWReachability.m - PROPERTIES COMPILE_FLAGS "-fobjc-arc") else() list(APPEND SRCS http/HttpClient_Curl.cpp @@ -344,4 +340,3 @@ message("-- Library will be installed to ${INSTALL_LIB_DIR}") # #target_link_libraries(mat PUBLIC libcurl.a libz.a libssl.a libcrypto.a "${SQLITE_LIBRARY}" "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) # #target_link_libraries(mat PUBLIC libsqlite3.a libz.a ${LIBS} "${CMAKE_THREAD_LIBS_INIT}" "${CMAKE_DL_LIBS}" ) #endif() - diff --git a/lib/pal/posix/NetworkInformationImpl.mm b/lib/pal/posix/NetworkInformationImpl.mm index 561707ab5..56b6714da 100644 --- a/lib/pal/posix/NetworkInformationImpl.mm +++ b/lib/pal/posix/NetworkInformationImpl.mm @@ -6,8 +6,8 @@ #include "pal/PAL.hpp" #include "pal/NetworkInformationImpl.hpp" +#import #import -#import "ODWReachability.h" namespace PAL_NS_BEGIN { @@ -64,21 +64,12 @@ virtual NetworkCost GetNetworkCost() private: void SetupModernNetDetect() API_AVAILABLE(macos(10.14), ios(12.0)); -#if ODW_LEGACY_REACHABILITY_REQUIRED - void SetupLegacyNetDetect(); -#endif void UpdateType(NetworkType type) noexcept; void UpdateCost(NetworkCost cost) noexcept; std::string m_network_provider {}; // iOS 12+ / macOS 10.14+ nw_path_monitor_t m_monitor = nil; - -#if ODW_LEGACY_REACHABILITY_REQUIRED - // Older Apple deployment targets still need the legacy fallback. - ODWReachability* m_reach = nil; - id m_notificationId = nil; -#endif }; NetworkInformation::NetworkInformation(IRuntimeConfig& configuration) : @@ -90,7 +81,6 @@ virtual NetworkCost GetNetworkCost() NetworkInformation::~NetworkInformation() noexcept { -#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { if (m_isNetDetectEnabled) @@ -101,23 +91,6 @@ virtual NetworkCost GetNetworkCost() } } } - else - { - if (m_isNetDetectEnabled) - { - [[NSNotificationCenter defaultCenter] removeObserver:m_notificationId]; - [m_reach stopNotifier]; - } - } -#else - if (m_isNetDetectEnabled) - { - if (m_monitor != nil) - { - nw_path_monitor_cancel(m_monitor); - } - } -#endif } void NetworkInformation::SetupModernNetDetect() @@ -181,67 +154,16 @@ virtual NetworkCost GetNetworkCost() } } -#if ODW_LEGACY_REACHABILITY_REQUIRED - void NetworkInformation::SetupLegacyNetDetect() - { - auto weak_this = std::weak_ptr(shared_from_this()); - - m_reach = [ODWReachability reachabilityForInternetConnection]; - void (^block)(NSNotification*) = ^(NSNotification*) - { - auto strong_this = weak_this.lock(); - if (!strong_this) - { - return; - } - - // NetworkCost information is not available until iOS 12. - // Just make the best guess here. - switch (m_reach.currentReachabilityStatus) - { - case NotReachable: - strong_this->UpdateType(NetworkType_Unknown); - strong_this->UpdateCost(NetworkCost_Unknown); - break; - case ReachableViaWiFi: - strong_this->UpdateType(NetworkType_Wifi); - strong_this->UpdateCost(NetworkCost_Unmetered); - break; - case ReachableViaWWAN: - strong_this->UpdateType(NetworkType_WWAN); - strong_this->UpdateCost(NetworkCost_Metered); - break; - } - }; - block(nil); // Update the initial status. - - if (m_isNetDetectEnabled) - { - m_notificationId = - [[NSNotificationCenter defaultCenter] - addObserverForName: kNetworkReachabilityChangedNotification - object: nil - queue: nil - usingBlock: block]; - [m_reach startNotifier]; - } - } -#endif - void NetworkInformation::SetupNetDetect() { -#if ODW_LEGACY_REACHABILITY_REQUIRED if (@available(macOS 10.14, iOS 12.0, *)) { SetupModernNetDetect(); } else { - SetupLegacyNetDetect(); + LOG_WARN("Network detection requires iOS 12.0 or macOS 10.14 or newer."); } -#else - SetupModernNetDetect(); -#endif } void NetworkInformation::UpdateType(NetworkType type) noexcept diff --git a/tests/unittests/obj-c/ODWReachabilityTests.mm b/tests/unittests/obj-c/ODWReachabilityTests.mm deleted file mode 100644 index 827406290..000000000 --- a/tests/unittests/obj-c/ODWReachabilityTests.mm +++ /dev/null @@ -1,133 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 -// -// ODWReachabilityTests.mm -// Tests -// -// Created by Abu Sayem on 13/12/2024. -// - -#import -#import "ODWReachability.h" - -#import -#import -#import - -@interface ODWReachabilityTests : XCTestCase -@end - -@implementation ODWReachabilityTests - -- (void)testReachabilityWithHostname -{ - NSString *hostname = @"www.microsoft.com"; - ODWReachability *reachability = [ODWReachability reachabilityWithHostname:hostname]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Reachability check"]; - NSString *hostUrl = [NSString stringWithFormat:@"https://%@", hostname]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - XCTAssertNotNil(reachability); - XCTAssertEqualObjects(reachability.url.absoluteString, hostUrl); - [expectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10.0 handler:nil]; -} - -- (void)testReachabilityWithInvalidHostname -{ - NSString *hostname = @"https://"; - ODWReachability *reachability = [ODWReachability reachabilityWithHostname:hostname]; - - XCTAssertNil(reachability); -} - -- (void)testReachabilityWithUnresolvedHostnameCreatesInstance -{ - NSString *hostname = @"invalid.hostname"; - ODWReachability *reachability = [ODWReachability reachabilityWithHostname:hostname]; - - XCTAssertNotNil(reachability); - XCTAssertEqualObjects(reachability.url.absoluteString, @"https://invalid.hostname"); -} - -- (void)testReachabilityWithHostAndPort -{ - NSString *hostname = @"example.com:8080"; - ODWReachability *reachability = [ODWReachability reachabilityWithHostname:hostname]; - - XCTAssertNotNil(reachability); - XCTAssertEqualObjects(reachability.url.absoluteString, @"https://example.com:8080"); -} - -- (void)testReachabilityWithAddress -{ - struct sockaddr_in address; - address.sin_family = AF_INET; - address.sin_addr.s_addr = inet_addr("8.8.8.8"); - ODWReachability *reachability = [ODWReachability reachabilityWithAddress:&address]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Reachability check"]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - XCTAssertNotNil(reachability); - XCTAssertEqualObjects(reachability.url.absoluteString, @"https://8.8.8.8"); - [expectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10.0 handler:nil]; -} - -- (void)testReachabilityWithInvalidAddress -{ - struct sockaddr_in address; - address.sin_family = AF_INET; - address.sin_addr.s_addr = inet_addr("0.0.0.0"); - ODWReachability *reachability = [ODWReachability reachabilityWithAddress:&address]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Reachability check"]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - XCTAssertNil(reachability); - [expectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10.0 handler:nil]; -} - -- (void)testReachabilityForInternetConnection -{ - ODWReachability *reachability = [ODWReachability reachabilityForInternetConnection]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Reachability check"]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - XCTAssertNotNil(reachability); - XCTAssertEqual(reachability.currentReachabilityStatus, ReachableViaWiFi); - [expectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10.0 handler:nil]; -} - -- (void)testReachabilityForLocalWiFi -{ - ODWReachability *reachability = [ODWReachability reachabilityForLocalWiFi]; - - XCTestExpectation *expectation = [self expectationWithDescription:@"Reachability check"]; - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - XCTAssertNotNil(reachability); - XCTAssertEqual(reachability.currentReachabilityStatus, ReachableViaWiFi); - [expectation fulfill]; - }); - [self waitForExpectationsWithTimeout:10.0 handler:nil]; -} - -- (void)testStartAndStopNotifier -{ - ODWReachability *reachability = [ODWReachability reachabilityForInternetConnection]; - XCTAssertNotNil(reachability); - - NSDate *start = [NSDate date]; - XCTAssertTrue([reachability startNotifier]); - XCTAssertLessThan([[NSDate date] timeIntervalSinceDate:start], 1.0); - - [reachability stopNotifier]; -} - -@end diff --git a/tests/unittests/unittests-ios.xcodeproj/project.pbxproj b/tests/unittests/unittests-ios.xcodeproj/project.pbxproj index 5511210d4..b17cd878a 100644 --- a/tests/unittests/unittests-ios.xcodeproj/project.pbxproj +++ b/tests/unittests/unittests-ios.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 16CC10702D4D58DA00C03295 /* ODWReachabilityTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16CC106F2D4D58DA00C03295 /* ODWReachabilityTests.mm */; }; 430102E5235E660F00836D50 /* iOSWrapper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 430102E4235E660F00836D50 /* iOSWrapper.mm */; }; 431EFE50233EBE54002FCC18 /* HttpClientTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 431EFE2A233EBE53002FCC18 /* HttpClientTests.cpp */; }; 431EFE51233EBE54002FCC18 /* LoggerTests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 431EFE2B233EBE53002FCC18 /* LoggerTests.cpp */; }; @@ -117,7 +116,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 16CC106F2D4D58DA00C03295 /* ODWReachabilityTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ODWReachabilityTests.mm; path = "obj-c/ODWReachabilityTests.mm"; sourceTree = ""; }; 430102E4235E660F00836D50 /* iOSWrapper.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = iOSWrapper.mm; path = ../../common/iOSWrapper.mm; sourceTree = ""; }; 431EFE1E233EBDF2002FCC18 /* libunittests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libunittests.a; sourceTree = BUILT_PRODUCTS_DIR; }; 431EFE2A233EBE53002FCC18 /* HttpClientTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HttpClientTests.cpp; sourceTree = ""; }; @@ -203,7 +201,6 @@ 431EFE15233EBDF2002FCC18 = { isa = PBXGroup; children = ( - 16CC106F2D4D58DA00C03295 /* ODWReachabilityTests.mm */, 43BD496F26B8B0C300EC3C0C /* EventPropertiesDecoratorTests.cpp */, 437C72F024D2286F0046F545 /* ODWLogConfigurationTests.mm */, 437C72EE24D21ACE0046F545 /* ODWSemanticContextTests.mm */, @@ -436,7 +433,6 @@ 431EFEA7233EC590002FCC18 /* HttpDeflateCompressionTests.cpp in Sources */, 431EFEB3233EC590002FCC18 /* OfflineStorageTests.cpp in Sources */, 431EFEB8233EC590002FCC18 /* TaskDispatcherCAPITests.cpp in Sources */, - 16CC10712D4D58DA00C03295 /* ODWReachabilityTests.mm in Sources */, 437C72ED24D0C5D70046F545 /* ODWEventPropertiesTests.mm in Sources */, 431EFEB6233EC590002FCC18 /* RouteTests.cpp in Sources */, 431EFEA8233EC590002FCC18 /* HttpRequestEncoderTests.cpp in Sources */, diff --git a/third_party/Reachability/ODWReachability.h b/third_party/Reachability/ODWReachability.h deleted file mode 100644 index 5d232be59..000000000 --- a/third_party/Reachability/ODWReachability.h +++ /dev/null @@ -1,137 +0,0 @@ -/* - Copyright (c) 2011, Tony Million. - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - */ - -#import -#import -#import -#import - - -/** - * Create NS_ENUM macro if it does not exist on the targeted version of iOS or OS X. - * - * @see http://nshipster.com/ns_enum-ns_options/ - **/ -#ifndef NS_ENUM -#define NS_ENUM(_type, _name) enum _name : _type _name; enum _name : _type -#endif - -extern NSString* const kNetworkReachabilityChangedNotification; - -// Older Apple deployment targets still need the legacy SCNetworkReachability -// backend at runtime. Newer targets can compile directly to the modern path. -#ifndef TARGET_OS_IOS -#define TARGET_OS_IOS 0 -#endif - -#if TARGET_OS_IOS -#define ODW_LEGACY_REACHABILITY_REQUIRED (__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_12_0) -#elif TARGET_OS_OSX -#define ODW_LEGACY_REACHABILITY_REQUIRED (__MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_14) -#else -#define ODW_LEGACY_REACHABILITY_REQUIRED 0 -#endif - -#define ODW_REACHABILITY_HAS_WWAN TARGET_OS_IOS - -typedef NS_ENUM(NSInteger, ODWNetworkStatus) { - // Apple NetworkStatus Compatible Names. - NotReachable = 0, - ReachableViaWiFi = 2, - ReachableViaWWAN = 1 -}; - -@class ODWReachability; - -typedef void (^NetworkReachable)(ODWReachability * reachability); -typedef void (^NetworkUnreachable)(ODWReachability * reachability); - - -@interface ODWReachability : NSObject - -@property (nonatomic, copy) NetworkReachable reachableBlock; -@property (nonatomic, copy) NetworkUnreachable unreachableBlock; - -@property (nonatomic, assign) BOOL reachableOnWWAN; - -@property (nonatomic, strong) NSURL *url; - -+(ODWReachability*)reachabilityWithHostname:(NSString*)hostname; -// This is identical to the function above, but is here to maintain -//compatibility with Apples original code. (see .m) -+(ODWReachability*)reachabilityWithHostName:(NSString*)hostname; -+(ODWReachability*)reachabilityForInternetConnection; -+(ODWReachability*)reachabilityWithAddress:(void *)hostAddress; -+(ODWReachability*)reachabilityForLocalWiFi; -+(void)setTimeoutDurationInSeconds:(int)timeoutDuration; - -// ------------------------------------------------------------------------- -// Behavior note for hostname / address constructors (modern Apple targets): -// -// Prior to the NWPathMonitor migration, instances created via -// +reachabilityWithHostname: or +reachabilityWithAddress: would, on -// iOS 12+ / macOS 10.14+, internally issue an NSURLSession HTTPS GET to -// the host's URL whenever -isReachable / -isReachableViaWiFi was queried, -// yielding a true per-endpoint reachability answer (DNS + TLS round-trip). -// -// After the migration, those queries return OS-level reachability for the -// hostname/address via SCNetworkReachabilityGetFlags on the SC ref the -// constructor created. That still involves DNS via the SC stack but is -// not the same as actually probing the endpoint with HTTPS. Callers that -// require an authoritative "can I reach this exact endpoint over HTTPS?" -// answer should perform that probe themselves rather than relying on -// these per-host instances. The SDK's own production network detection -// (NetworkInformationImpl) uses +reachabilityForInternetConnection on -// legacy targets and nw_path_monitor_create() directly on modern targets; -// neither path was ever per-endpoint, so this change does not affect the -// SDK's own behavior. -// ------------------------------------------------------------------------- - --(ODWReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref; - --(BOOL)startNotifier; --(void)stopNotifier; - --(BOOL)isReachable; --(BOOL)isReachableViaWWAN; --(BOOL)isReachableViaWiFi; - -// WWAN may be available, but not active until a connection has been established. -// WiFi may require a connection for VPN on Demand. --(BOOL)isConnectionRequired; // Identical DDG variant. --(BOOL)connectionRequired; // Apple's routine. -// Dynamic, on demand connection? --(BOOL)isConnectionOnDemand; -// Is user intervention required? --(BOOL)isInterventionRequired; - --(ODWNetworkStatus)currentReachabilityStatus; --(SCNetworkReachabilityFlags)reachabilityFlags; --(NSString*)currentReachabilityString; --(NSString*)currentReachabilityFlags; - -@end diff --git a/third_party/Reachability/ODWReachability.m b/third_party/Reachability/ODWReachability.m deleted file mode 100644 index 1c802f6e2..000000000 --- a/third_party/Reachability/ODWReachability.m +++ /dev/null @@ -1,1067 +0,0 @@ -/* - Copyright (c) 2011, Tony Million. - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. - */ - -#import "ODWReachability.h" - -#import - -#import -#import -#import - - -NSString *const kNetworkReachabilityChangedNotification = @"NetworkReachabilityChangedNotification"; -static char ODWReachabilityQueueKey; - -typedef struct -{ - nw_path_status_t status; - BOOL usesWiFi; - BOOL usesWWAN; - BOOL hasObservedPath; -} ODWModernPathSnapshot; - -@class ODWReachability; - -@interface ODWReachabilityMonitorContext : NSObject - -@property (nonatomic, weak) ODWReachability *owner; - -@end - -@implementation ODWReachabilityMonitorContext -@end - - -@interface ODWReachability () - -@property (nonatomic, assign) SCNetworkReachabilityRef reachabilityRef; -@property (nonatomic, strong) dispatch_queue_t reachabilitySerialQueue; -@property (nonatomic, strong) id reachabilityObject; -@property (nonatomic, strong) nw_path_monitor_t pathMonitor; -@property (nonatomic, strong) ODWReachabilityMonitorContext *pathMonitorContext; -@property (nonatomic, strong) dispatch_group_t initialPathGroup; -@property (nonatomic, assign) nw_path_status_t currentPathStatus; -@property (nonatomic, assign) BOOL currentPathUsesWiFi; -@property (nonatomic, assign) BOOL currentPathUsesWWAN; -@property (nonatomic, assign) BOOL hasObservedPath; -@property (nonatomic, assign) BOOL monitorLocalWiFiOnly; - --(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags; --(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags; --(BOOL)getReachabilityFlags:(SCNetworkReachabilityFlags *)flags; --(BOOL)startLegacyNotifier; --(void)stopLegacyNotifier; --(BOOL)ensureModernPathMonitor API_AVAILABLE(macos(10.14), ios(12.0)); --(BOOL)awaitModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); --(ODWModernPathSnapshot)currentModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); --(void)resetModernPathSnapshot API_AVAILABLE(macos(10.14), ios(12.0)); --(void)handleModernPathUpdate:(nw_path_t)path API_AVAILABLE(macos(10.14), ios(12.0)); --(void)notifyModernPathChange API_AVAILABLE(macos(10.14), ios(12.0)); - -@end - - -static NSString *reachabilityFlags(SCNetworkReachabilityFlags flags) -{ - return [NSString stringWithFormat:@"%c%c %c%c%c%c%c%c%c", -#if ODW_REACHABILITY_HAS_WWAN - (flags & kSCNetworkReachabilityFlagsIsWWAN) ? 'W' : '-', -#else - 'X', -#endif - (flags & kSCNetworkReachabilityFlagsReachable) ? 'R' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionRequired) ? 'c' : '-', - (flags & kSCNetworkReachabilityFlagsTransientConnection) ? 't' : '-', - (flags & kSCNetworkReachabilityFlagsInterventionRequired) ? 'i' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) ? 'C' : '-', - (flags & kSCNetworkReachabilityFlagsConnectionOnDemand) ? 'D' : '-', - (flags & kSCNetworkReachabilityFlagsIsLocalAddress) ? 'l' : '-', - (flags & kSCNetworkReachabilityFlagsIsDirect) ? 'd' : '-']; -} - -static BOOL ODWModernPathIsReachable(nw_path_status_t status) API_AVAILABLE(macos(10.14), ios(12.0)) -{ - return status == nw_path_status_satisfied || status == nw_path_status_satisfiable; -} - -// Start listening for reachability notifications on the current run loop -static void TMReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info) -{ -#pragma unused (target) - - ODWReachability *reachability = ((__bridge ODWReachability*)info); - - // We probably don't need an autoreleasepool here, as GCD docs state each queue has its own autorelease pool, - // but what the heck eh? - @autoreleasepool - { - [reachability reachabilityChanged:flags]; - } -} - - -@implementation ODWReachability - -static int kTimeoutDurationInSeconds = 10; - -#pragma mark - Class Constructor Methods - -+(ODWReachability*)reachabilityWithHostName:(NSString*)hostname -{ - return [ODWReachability reachabilityWithHostname:hostname]; -} - -+(instancetype)reachabilityWithHostname:(NSString*)hostname -{ - if (hostname == nil || [hostname length] == 0) - { - NSLog(@"Invalid hostname '%@': hostname is empty", hostname); - return nil; - } - - BOOL hasExplicitScheme = [hostname rangeOfString:@"://"].location != NSNotFound; - NSString *urlString = hasExplicitScheme ? hostname : [NSString stringWithFormat:@"https://%@", hostname]; - NSURLComponents *components = [NSURLComponents componentsWithString:urlString]; - if (components == nil || [components.host length] == 0) - { - NSLog(@"Invalid hostname '%@': URL has no host", hostname); - return nil; - } - - NSString *reachabilityHost = components.host; - NSURL *url = components.URL; - if (url == nil) - { - NSLog(@"Invalid hostname '%@': URL could not be constructed", hostname); - return nil; - } - - // NWPathMonitor has no public hostname-targeted API: it monitors the system - // network path, not per-host reachability. Hostname-based reachability still - // routes through SCNetworkReachabilityCreateWithName, with the deprecated-API - // warning locally suppressed. The modern path-monitor backend is used by the - // hostname-agnostic isReachable* methods below. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithName(NULL, [reachabilityHost UTF8String]); -#pragma clang diagnostic pop - if (ref) - { - ODWReachability *reachability = [[self alloc] initWithReachabilityRef:ref]; - reachability.url = url; - - return reachability; - } - - const char *errorString = SCErrorString(SCError()); - NSLog(@"Invalid hostname '%@': SCNetworkReachabilityCreateWithName failed for '%@' (%s)", - hostname, - reachabilityHost, - errorString != NULL ? errorString : "unknown error"); - return nil; -} - -+(ODWReachability *)reachabilityWithAddress:(void *)hostAddress -{ - if (hostAddress == NULL) - { - NSLog(@"Invalid address: address pointer is null"); - return nil; - } - - struct sockaddr_storage addressStorage; - bzero(&addressStorage, sizeof(addressStorage)); - struct sockaddr *address = (struct sockaddr *)hostAddress; - NSURL *url = nil; - if (address->sa_family == AF_INET) - { - char addressString[INET_ADDRSTRLEN] = { 0 }; - struct sockaddr_in *ipv4Address = (struct sockaddr_in *)&addressStorage; - *ipv4Address = *(struct sockaddr_in *)hostAddress; - // BSD sockaddr_in callers (including the unit tests) don't reliably - // initialize sin_len; setting it ourselves avoids passing a garbage - // length to SCNetworkReachabilityCreateWithAddress. - ipv4Address->sin_len = sizeof(*ipv4Address); - // Reject the unspecified IPv4 wildcard (INADDR_ANY / 0.0.0.0). It is not a - // routable host address; the SDK only uses it internally as the legacy - // "internet anywhere" SC probe, which goes through a private path that - // bypasses this validator. - if (ipv4Address->sin_addr.s_addr == htonl(INADDR_ANY)) - { - NSLog(@"Invalid address: IPv4 unspecified address (0.0.0.0) is not a valid host"); - return nil; - } - address = (struct sockaddr *)ipv4Address; - if (inet_ntop(AF_INET, &ipv4Address->sin_addr, addressString, sizeof(addressString)) != NULL) - { - url = [NSURL URLWithString:[NSString stringWithFormat:@"https://%s", addressString]]; - } - } - else if (address->sa_family == AF_INET6) - { - char addressString[INET6_ADDRSTRLEN] = { 0 }; - struct sockaddr_in6 *ipv6Address = (struct sockaddr_in6 *)&addressStorage; - *ipv6Address = *(struct sockaddr_in6 *)hostAddress; - // BSD sockaddr_in6 callers don't reliably initialize sin6_len; setting - // it ourselves avoids passing a garbage length to - // SCNetworkReachabilityCreateWithAddress. - ipv6Address->sin6_len = sizeof(*ipv6Address); - // Reject the unspecified IPv6 wildcard (in6addr_any / ::), same reasoning as IPv4. - if (memcmp(&ipv6Address->sin6_addr, &in6addr_any, sizeof(struct in6_addr)) == 0) - { - NSLog(@"Invalid address: IPv6 unspecified address (::) is not a valid host"); - return nil; - } - address = (struct sockaddr *)ipv6Address; - if (inet_ntop(AF_INET6, &ipv6Address->sin6_addr, addressString, sizeof(addressString)) != NULL) - { - url = [NSURL URLWithString:[NSString stringWithFormat:@"https://[%s]", addressString]]; - } - } - else - { - NSLog(@"Invalid address: unsupported sa_family %d (expected AF_INET or AF_INET6)", address->sa_family); - return nil; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr*)address); -#pragma clang diagnostic pop - if (ref) - { - ODWReachability *reachability = [[self alloc] initWithReachabilityRef:ref]; - reachability.url = url; - - return reachability; - } - - const char *errorString = SCErrorString(SCError()); - NSLog(@"Invalid address: SCNetworkReachabilityCreateWithAddress failed (%s)", - errorString != NULL ? errorString : "unknown error"); - return nil; -} - -+(ODWReachability *)reachabilityForInternetConnection -{ - if (@available(macOS 10.14, iOS 12.0, *)) - { - return [[self alloc] init]; - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - // Legacy SC fallback for deployment targets older than macOS 10.14 / iOS 12. - // Apple's reference Reachability uses the zero IPv4 address (INADDR_ANY) here - // as a "probe any internet" sentinel — the public +reachabilityWithAddress: - // now rejects that wildcard, so create the SC ref directly and bypass the - // validator. - struct sockaddr_in zeroAddress; - bzero(&zeroAddress, sizeof(zeroAddress)); - zeroAddress.sin_len = sizeof(zeroAddress); - zeroAddress.sin_family = AF_INET; - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - SCNetworkReachabilityRef ref = SCNetworkReachabilityCreateWithAddress( - kCFAllocatorDefault, (const struct sockaddr*)&zeroAddress); -#pragma clang diagnostic pop - if (ref) - { - return [[self alloc] initWithReachabilityRef:ref]; - } -#endif - return nil; -} - -+(ODWReachability*)reachabilityForLocalWiFi -{ - if (@available(macOS 10.14, iOS 12.0, *)) - { - ODWReachability *reachability = [[self alloc] init]; - reachability.monitorLocalWiFiOnly = YES; - return reachability; - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - // Legacy SC fallback for deployment targets older than macOS 10.14 / iOS 12. - struct sockaddr_in localWifiAddress; - bzero(&localWifiAddress, sizeof(localWifiAddress)); - localWifiAddress.sin_len = sizeof(localWifiAddress); - localWifiAddress.sin_family = AF_INET; - // IN_LINKLOCALNETNUM is defined in as 169.254.0.0 - localWifiAddress.sin_addr.s_addr = htonl(IN_LINKLOCALNETNUM); - - return [self reachabilityWithAddress:&localWifiAddress]; -#else - return nil; -#endif -} - - -// Initialization methods - --(instancetype)init -{ - self = [super init]; - if (self != nil) - { - self.reachableOnWWAN = YES; - self.reachabilitySerialQueue = dispatch_queue_create("com.tonymillion.reachability", NULL); - dispatch_queue_set_specific(self.reachabilitySerialQueue, - &ODWReachabilityQueueKey, - &ODWReachabilityQueueKey, - NULL); - } - - return self; -} - --(ODWReachability *)initWithReachabilityRef:(SCNetworkReachabilityRef)ref -{ - self = [self init]; - if (self != nil) - { - self.reachabilityRef = ref; - } - - return self; -} - --(ODWModernPathSnapshot)currentModernPathSnapshot -{ - __block ODWModernPathSnapshot snapshot = { nw_path_status_invalid, NO, NO, NO }; - void (^copySnapshot)(void) = ^{ - snapshot.status = self.currentPathStatus; - snapshot.usesWiFi = self.currentPathUsesWiFi; - snapshot.usesWWAN = self.currentPathUsesWWAN; - snapshot.hasObservedPath = self.hasObservedPath; - }; - - if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) - { - copySnapshot(); - } - else - { - dispatch_sync(self.reachabilitySerialQueue, copySnapshot); - } - - return snapshot; -} - --(void)resetModernPathSnapshot -{ - void (^resetSnapshot)(void) = ^{ - self.hasObservedPath = NO; - self.currentPathStatus = nw_path_status_invalid; - self.currentPathUsesWiFi = NO; - self.currentPathUsesWWAN = NO; - }; - - if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) - { - resetSnapshot(); - } - else - { - dispatch_sync(self.reachabilitySerialQueue, resetSnapshot); - } -} - --(BOOL)ensureModernPathMonitor -{ - __block BOOL ensured = NO; - void (^ensureMonitor)(void) = ^{ - if (self.pathMonitor != nil) - { - ensured = YES; - return; - } - - nw_path_monitor_t monitor = self.monitorLocalWiFiOnly - ? nw_path_monitor_create_with_type(nw_interface_type_wifi) - : nw_path_monitor_create(); - - if (monitor == nil) - { - return; - } - - [self resetModernPathSnapshot]; - self.initialPathGroup = dispatch_group_create(); - dispatch_group_enter(self.initialPathGroup); - self.pathMonitor = monitor; - - ODWReachabilityMonitorContext *context = [[ODWReachabilityMonitorContext alloc] init]; - context.owner = self; - self.pathMonitorContext = context; - - nw_path_monitor_set_queue(self.pathMonitor, self.reachabilitySerialQueue); - nw_path_monitor_set_update_handler(self.pathMonitor, ^(nw_path_t path) { - ODWReachability *owner = context.owner; - if (owner == nil) - { - return; - } - - [owner handleModernPathUpdate:path]; - }); - nw_path_monitor_start(self.pathMonitor); - ensured = YES; - }; - - if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) - { - ensureMonitor(); - } - else - { - dispatch_sync(self.reachabilitySerialQueue, ensureMonitor); - } - - return ensured; -} - --(BOOL)awaitModernPathSnapshot -{ - if (![self ensureModernPathMonitor]) - { - return NO; - } - - if ([self currentModernPathSnapshot].hasObservedPath) - { - return YES; - } - - // Capture the group into a local so a concurrent -stopNotifier on - // another thread cannot release the property between the nil-check and - // the wait below. - __block dispatch_group_t group = nil; - void (^copyInitialPathGroup)(void) = ^{ - group = self.initialPathGroup; - }; - if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) - { - copyInitialPathGroup(); - } - else - { - dispatch_sync(self.reachabilitySerialQueue, copyInitialPathGroup); - } - - if (group == nil) - { - return NO; - } - - // Avoid blocking reachability queries on the main thread before the first - // NWPathMonitor update arrives. Callers get a conservative "unknown yet" - // result until the async update handler records the first snapshot. - if ([NSThread isMainThread]) - { - return NO; - } - // The update handler runs on this serial queue, so waiting here would deadlock it. - if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) - { - return NO; - } - - long waitResult = dispatch_group_wait( - group, - dispatch_time(DISPATCH_TIME_NOW, kTimeoutDurationInSeconds * NSEC_PER_SEC)); - return waitResult == 0 && [self currentModernPathSnapshot].hasObservedPath; -} - --(void)handleModernPathUpdate:(nw_path_t)path -{ - self.currentPathStatus = nw_path_get_status(path); - self.currentPathUsesWiFi = nw_path_uses_interface_type(path, nw_interface_type_wifi); -#if ODW_REACHABILITY_HAS_WWAN - self.currentPathUsesWWAN = nw_path_uses_interface_type(path, nw_interface_type_cellular); -#else - self.currentPathUsesWWAN = NO; -#endif - - BOOL firstPath = !self.hasObservedPath; - self.hasObservedPath = YES; - if (firstPath) - { - dispatch_group_t group = self.initialPathGroup; - if (group != nil) - { - dispatch_group_leave(group); - } - } - - if (self.reachabilityObject == self) - { - [self notifyModernPathChange]; - } -} - --(void)notifyModernPathChange -{ - ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; - if (ODWModernPathIsReachable(snapshot.status)) - { - if (self.reachableBlock) - { - self.reachableBlock(self); - } - } - else if (self.unreachableBlock) - { - self.unreachableBlock(self); - } - - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkReachabilityChangedNotification - object:self]; - }); -} - -+(void)setTimeoutDurationInSeconds:(int)timeoutDuration -{ - if (timeoutDuration >= kTimeoutDurationInSeconds) - { - kTimeoutDurationInSeconds = timeoutDuration; - } - else - { - NSLog(@"Timeout duration must be at least 10."); - } -} - --(void)dealloc -{ - [self stopNotifier]; - - if(self.reachabilityRef) - { - CFRelease(self.reachabilityRef); - self.reachabilityRef = nil; - } - - self.reachableBlock = nil; - self.unreachableBlock = nil; - self.reachabilitySerialQueue = nil; -} - -#pragma mark - Notifier Methods - -// Notifier -// NOTE: This uses GCD to trigger the blocks - they *WILL NOT* be called on THE MAIN THREAD -// - In other words DO NOT DO ANY UI UPDATES IN THE BLOCKS. -// INSTEAD USE dispatch_async(dispatch_get_main_queue(), ^{UISTUFF}) (or dispatch_sync if you want) - --(BOOL)startNotifier -{ - if (self.reachabilityRef != nil) - { - return [self startLegacyNotifier]; - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) - { -#endif - // Use NWPathMonitor for macOS 10.14 or higher. - if ([self ensureModernPathMonitor]) - { - self.reachabilityObject = self; - return YES; - } - return NO; -#if ODW_LEGACY_REACHABILITY_REQUIRED - } - - return NO; -#endif -} - --(BOOL)startLegacyNotifier -{ - // Allow start notifier to be called multiple times. - if (self.reachabilityObject && (self.reachabilityObject == self)) - { - return YES; - } - - SCNetworkReachabilityContext context = { 0, NULL, NULL, NULL, NULL }; - context.info = (__bridge void *)self; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - BOOL callbackSet = SCNetworkReachabilitySetCallback(self.reachabilityRef, TMReachabilityCallback, &context); - BOOL queueSet = callbackSet && SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, self.reachabilitySerialQueue); -#pragma clang diagnostic pop - if (callbackSet) - { - if (queueSet) - { - self.reachabilityObject = self; - return YES; - } else { -#ifdef DEBUG - NSLog(@"SCNetworkReachabilitySetDispatchQueue() failed: %s", SCErrorString(SCError())); -#endif - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // UH OH - FAILURE - stop any callbacks! - SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); -#pragma clang diagnostic pop - } - } - else - { -#ifdef DEBUG - NSLog(@"SCNetworkReachabilitySetCallback() failed: %s", SCErrorString(SCError())); -#endif - } - - // if we get here we fail at the internet - self.reachabilityObject = nil; - return NO; -} - --(void)stopNotifier -{ - if (self.reachabilityRef != nil) - { - [self stopLegacyNotifier]; - return; - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) - { -#endif - // Use NWPathMonitor for macOS 10.14 or higher. - void (^stopMonitor)(void) = ^{ - dispatch_group_t group = self.initialPathGroup; - BOOL shouldLeaveGroup = group != nil && !self.hasObservedPath; - - self.reachabilityObject = nil; - if (self.pathMonitor != nil) - { - self.pathMonitorContext.owner = nil; - nw_path_monitor_cancel(self.pathMonitor); - self.pathMonitor = nil; - } - self.pathMonitorContext = nil; - [self resetModernPathSnapshot]; - if (shouldLeaveGroup) - { - dispatch_group_leave(group); - } - self.initialPathGroup = nil; - }; - - if (dispatch_get_specific(&ODWReachabilityQueueKey) == &ODWReachabilityQueueKey) - { - stopMonitor(); - } - else - { - dispatch_sync(self.reachabilitySerialQueue, stopMonitor); - } -#if ODW_LEGACY_REACHABILITY_REQUIRED - return; - } - - return; -#endif -} - --(void)stopLegacyNotifier -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - // First stop, any callbacks! - SCNetworkReachabilitySetCallback(self.reachabilityRef, NULL, NULL); - - // Unregister target from the GCD serial dispatch queue. - SCNetworkReachabilitySetDispatchQueue(self.reachabilityRef, NULL); -#pragma clang diagnostic pop - self.reachabilityObject = nil; -} - - -#pragma mark - reachability tests - -// This is for the case where you flick the airplane mode; -// you end up getting something like this: -//Reachability: WR ct----- -//Reachability: -- ------- -//Reachability: WR ct----- -//Reachability: -- ------- -// We treat this as 4 UNREACHABLE triggers - really apple should do better than this - -#define testcase (kSCNetworkReachabilityFlagsConnectionRequired | kSCNetworkReachabilityFlagsTransientConnection) - --(BOOL)isReachableWithFlags:(SCNetworkReachabilityFlags)flags -{ - BOOL connectionUP = YES; - - if(!(flags & kSCNetworkReachabilityFlagsReachable)) - connectionUP = NO; - - if( (flags & testcase) == testcase ) - connectionUP = NO; - -#if ODW_REACHABILITY_HAS_WWAN - if(flags & kSCNetworkReachabilityFlagsIsWWAN) - { - // We're on 3G. - if(!self.reachableOnWWAN) - { - // We don't want to connect when on 3G. - connectionUP = NO; - } - } -#endif - - return connectionUP; -} - --(BOOL)getReachabilityFlags:(SCNetworkReachabilityFlags *)flags -{ - if (self.reachabilityRef == nil) - { - return NO; - } - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - BOOL result = SCNetworkReachabilityGetFlags(self.reachabilityRef, flags); -#pragma clang diagnostic pop - return result; -} - --(BOOL)isReachable -{ - if (self.reachabilityRef != nil) - { - SCNetworkReachabilityFlags flags; - return [self getReachabilityFlags:&flags] && [self isReachableWithFlags:flags]; - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) - { -#endif - if (![self awaitModernPathSnapshot]) - { - return NO; - } - - ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; - return ODWModernPathIsReachable(snapshot.status); -#if ODW_LEGACY_REACHABILITY_REQUIRED - } - - return NO; -#endif -} - - --(BOOL)isReachableViaWWAN -{ -#if ODW_REACHABILITY_HAS_WWAN - if (self.reachabilityRef != nil) - { - SCNetworkReachabilityFlags flags = 0; - return [self getReachabilityFlags:&flags] && - (flags & kSCNetworkReachabilityFlagsReachable) && - (flags & kSCNetworkReachabilityFlagsIsWWAN); - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) - { -#endif - if (![self awaitModernPathSnapshot]) - { - return NO; - } - - ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; - return ODWModernPathIsReachable(snapshot.status) && snapshot.usesWWAN; -#if ODW_LEGACY_REACHABILITY_REQUIRED - } - - return NO; -#endif -#else - return NO; -#endif -} - --(BOOL)isReachableViaWiFi -{ - if (self.reachabilityRef != nil) - { - SCNetworkReachabilityFlags flags = 0; - if ([self getReachabilityFlags:&flags] && (flags & kSCNetworkReachabilityFlagsReachable)) - { -#if ODW_REACHABILITY_HAS_WWAN - if (flags & kSCNetworkReachabilityFlagsIsWWAN) - { - return NO; - } -#endif - return YES; - } - - return NO; - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) - { -#endif - if (![self awaitModernPathSnapshot]) - { - return NO; - } - - ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; - if (!ODWModernPathIsReachable(snapshot.status)) - { - return NO; - } -#if ODW_REACHABILITY_HAS_WWAN - if (self.monitorLocalWiFiOnly) - { - return snapshot.usesWiFi; - } - - return !snapshot.usesWWAN; -#else - return self.monitorLocalWiFiOnly ? snapshot.usesWiFi : YES; -#endif -#if ODW_LEGACY_REACHABILITY_REQUIRED - } - - return NO; -#endif -} - - -// WWAN may be available, but not active until a connection has been established. -// WiFi may require a connection for VPN on Demand. --(BOOL)isConnectionRequired -{ - return [self connectionRequired]; -} - --(BOOL)connectionRequired -{ - if (self.reachabilityRef != nil) - { - SCNetworkReachabilityFlags flags; - return [self getReachabilityFlags:&flags] && - (flags & kSCNetworkReachabilityFlagsConnectionRequired); - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) - { -#endif - if (![self awaitModernPathSnapshot]) - { - return NO; - } - - ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; - return snapshot.status == nw_path_status_satisfiable; -#if ODW_LEGACY_REACHABILITY_REQUIRED - } - - return NO; -#endif -} - - -// Dynamic, on demand connection? --(BOOL)isConnectionOnDemand -{ - if (self.reachabilityRef != nil) - { - SCNetworkReachabilityFlags flags; - if (![self getReachabilityFlags:&flags]) - { - return NO; - } - - return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && - (flags & (kSCNetworkReachabilityFlagsConnectionOnTraffic | kSCNetworkReachabilityFlagsConnectionOnDemand))); - } - - return NO; -} - - -// Is user intervention required? --(BOOL)isInterventionRequired -{ - if (self.reachabilityRef != nil) - { - SCNetworkReachabilityFlags flags; - if (![self getReachabilityFlags:&flags]) - { - return NO; - } - - return ((flags & kSCNetworkReachabilityFlagsConnectionRequired) && - (flags & kSCNetworkReachabilityFlagsInterventionRequired)); - } - - return NO; -} - - - -#pragma mark - reachability status stuff - --(ODWNetworkStatus)currentReachabilityStatus -{ - if([self isReachable]) - { - if([self isReachableViaWiFi]) - return ReachableViaWiFi; - -#if ODW_REACHABILITY_HAS_WWAN - return ReachableViaWWAN; -#endif - } - - return NotReachable; -} - --(SCNetworkReachabilityFlags)reachabilityFlags -{ - if (self.reachabilityRef != nil) - { - SCNetworkReachabilityFlags flags = 0; - return [self getReachabilityFlags:&flags] ? flags : 0; - } - -#if ODW_LEGACY_REACHABILITY_REQUIRED - if (@available(macOS 10.14, iOS 12.0, *)) - { -#endif - if (![self awaitModernPathSnapshot]) - { - return 0; - } - - ODWModernPathSnapshot snapshot = [self currentModernPathSnapshot]; - SCNetworkReachabilityFlags flags = 0; - if (ODWModernPathIsReachable(snapshot.status)) - { - flags |= kSCNetworkReachabilityFlagsReachable; - } - if (snapshot.status == nw_path_status_satisfiable) - { - flags |= kSCNetworkReachabilityFlagsConnectionRequired; - } -#if ODW_REACHABILITY_HAS_WWAN - if (snapshot.usesWWAN) - { - flags |= kSCNetworkReachabilityFlagsIsWWAN; - } -#endif - return flags; -#if ODW_LEGACY_REACHABILITY_REQUIRED - } - - return 0; -#endif -} - --(NSString*)currentReachabilityString -{ - ODWNetworkStatus temp = [self currentReachabilityStatus]; - - if(temp == ReachableViaWWAN) - { - // Updated for the fact that we have CDMA phones now! - return NSLocalizedString(@"Cellular", @""); - } - if (temp == ReachableViaWiFi) - { - return NSLocalizedString(@"WiFi", @""); - } - - return NSLocalizedString(@"No Connection", @""); -} - --(NSString*)currentReachabilityFlags -{ - return reachabilityFlags([self reachabilityFlags]); -} - -#pragma mark - Callback function calls this method - --(void)reachabilityChanged:(SCNetworkReachabilityFlags)flags -{ - if([self isReachableWithFlags:flags]) - { - if(self.reachableBlock) - { - self.reachableBlock(self); - } - } - else - { - if(self.unreachableBlock) - { - self.unreachableBlock(self); - } - } - - // this makes sure the change notification happens on the MAIN THREAD - dispatch_async(dispatch_get_main_queue(), ^{ - [[NSNotificationCenter defaultCenter] postNotificationName:kNetworkReachabilityChangedNotification - object:self]; - }); -} - -#pragma mark - Debug Description - -- (NSString *) description -{ - NSString *description = [NSString stringWithFormat:@"<%@: %p (%@)>", - NSStringFromClass([self class]), self, [self currentReachabilityFlags]]; - return description; -} - -@end diff --git a/third_party/Reachability/README.md b/third_party/Reachability/README.md deleted file mode 100644 index 5feefa7ab..000000000 --- a/third_party/Reachability/README.md +++ /dev/null @@ -1,28 +0,0 @@ -This code has been obtained from https://github.com/tonymillion/Reachability - -LICENSE: https://github.com/tonymillion/Reachability/blob/master/LICENCE.txt - -Copyright (c) 2011-2013, Tony Million. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/cgmanifest.json b/third_party/cgmanifest.json index dbda55d75..578ed0be4 100644 --- a/third_party/cgmanifest.json +++ b/third_party/cgmanifest.json @@ -1,16 +1,6 @@ { "$schema": "https://json.schemastore.org/component-detection-manifest.json", "Registrations": [ - { - "Component": { - "Type": "git", - "git": { - "RepositoryUrl": "https://github.com/tonymillion/Reachability", - "CommitHash": "788111ebf98e86368cd03da17f884015d2fc1543" - } - }, - "DevelopmentDependency": false - }, { "Component": { "Type": "git",