From 4b62d73d47b71f3113b91ab228dad7cfc3a317d0 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Wed, 29 Apr 2026 15:00:32 -0700 Subject: [PATCH 01/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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/13] 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 }