Skip to content
220 changes: 124 additions & 96 deletions lib/pal/posix/NetworkInformationImpl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
class NetworkInformation : public NetworkInformationImpl,
public std::enable_shared_from_this<NetworkInformation>
{
public:
public:
/// <summary>
///
/// </summary>
Expand Down Expand Up @@ -59,21 +59,27 @@ virtual NetworkCost GetNetworkCost()
/// <summary>
/// Setup initial network information and start net monitor if requested.
/// This cannot be put in constructor because we need to use shared_from_this.
/// </summary>
void SetupNetDetect();
/// </summary>
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)
Expand All @@ -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)
Expand All @@ -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<NetworkInformation>(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<NetworkInformation>(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<NetworkInformation>(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<NetworkInformation>(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
Expand Down Expand Up @@ -229,4 +258,3 @@ virtual NetworkCost GetNetworkCost()
}

} PAL_NS_END

12 changes: 12 additions & 0 deletions third_party/Reachability/ODWReachability.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
POSSIBILITY OF SUCH DAMAGE.
*/

#import <AvailabilityMacros.h>
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <TargetConditionals.h>


/**
Expand All @@ -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,
Expand Down
Loading
Loading