using System.Diagnostics.CodeAnalysis; using System.Net; using CoreFoundation; using CoreTelephony; using SystemConfiguration; namespace Blahblah.Library.Network; [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "")] partial class Connectivity { static readonly Lazy cellularData = new(() => new CTCellularData()); static ReachabilityListener? listener; private static partial NetworkStatus PlatformGetStatus() { var restricted = cellularData.Value.RestrictedState == CTCellularDataRestrictedState.Restricted; var internetStatus = InternetConnectionStatus(); if (internetStatus == NetworkStatus.ReachableViaCarrierDataNetwork && !restricted) { return NetworkStatus.ReachableViaCarrierDataNetwork; } if (internetStatus == NetworkStatus.ReachableViaWiFiNetwork) { return NetworkStatus.ReachableViaWiFiNetwork; } var remoteStatus = RemoteHostStatus(); if (remoteStatus == NetworkStatus.ReachableViaCarrierDataNetwork && !restricted) { return NetworkStatus.ReachableViaCarrierDataNetwork; } if (remoteStatus == NetworkStatus.ReachableViaWiFiNetwork) { return NetworkStatus.ReachableViaWiFiNetwork; } return NetworkStatus.NotReachable; } static NetworkStatus RemoteHostStatus() { using var remote = new NetworkReachability(hostName); if (!remote.TryGetFlags(out var flags)) { return NetworkStatus.NotReachable; } if (!IsReachableWithoutRequiringConnection(flags)) { return NetworkStatus.NotReachable; } if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) { return NetworkStatus.ReachableViaCarrierDataNetwork; } return NetworkStatus.ReachableViaWiFiNetwork; } static bool IsReachableWithoutRequiringConnection(NetworkReachabilityFlags flags) { // Is it reachable with the current network configuration? var isReachable = (flags & NetworkReachabilityFlags.Reachable) != 0; // Do we need a connection to reach it? var noConnectionRequired = (flags & NetworkReachabilityFlags.ConnectionRequired) == 0; // Since the network stack will automatically try to get the WAN up, // probe that if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) { noConnectionRequired = true; } return isReachable && noConnectionRequired; } static NetworkStatus InternetConnectionStatus() { var status = NetworkStatus.NotReachable; var defaultNetworkAvailable = IsNetworkAvailable(out var flags); // If the connection is reachable and no connection is required, then assume it's WiFi if (defaultNetworkAvailable) { status = NetworkStatus.ReachableViaWiFiNetwork; } else if ((flags & NetworkReachabilityFlags.IsWWAN) != 0) { // If it's a WWAN connection.. status = NetworkStatus.ReachableViaCarrierDataNetwork; } // If the connection is on-demand or on-traffic and no user intervention // is required, then assume WiFi. if (((flags & NetworkReachabilityFlags.ConnectionOnDemand) != 0 || (flags & NetworkReachabilityFlags.ConnectionOnTraffic) != 0) && (flags & NetworkReachabilityFlags.InterventionRequired) == 0) { status = NetworkStatus.ReachableViaWiFiNetwork; } return status; } private static bool IsNetworkAvailable(out NetworkReachabilityFlags flags) { var ip = new IPAddress(0); using var route = new NetworkReachability(ip); if (!route.TryGetFlags(out flags)) { return false; } return IsReachableWithoutRequiringConnection(flags); } private static partial void StartListeners() { StopListeners(); listener = new ReachabilityListener(); listener.ReachabilityChanged += OnConnectivityChanged; } private static partial void StopListeners() { if (listener == null) { return; } listener.ReachabilityChanged -= OnConnectivityChanged; listener.Dispose(); listener = null; } class ReachabilityListener : IDisposable { NetworkReachability? routeReachability; NetworkReachability? remoteReachability; public event Action? ReachabilityChanged; public ReachabilityListener() { var ip = new IPAddress(0); routeReachability = new NetworkReachability(ip); routeReachability.SetNotification(OnChange); routeReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); remoteReachability = new NetworkReachability(hostName); // Need to probe before we queue, or we wont get any meaningful values // this only happens when you create NetworkReachability from a hostname remoteReachability.TryGetFlags(out var flags); remoteReachability.SetNotification(OnChange); remoteReachability.Schedule(CFRunLoop.Main, CFRunLoop.ModeDefault); cellularData.Value.RestrictionDidUpdateNotifier = new(OnRestrictedStateChanged); } public void Dispose() { routeReachability?.Dispose(); routeReachability = null; remoteReachability?.Dispose(); remoteReachability = null; cellularData.Value.RestrictionDidUpdateNotifier = null; } void OnRestrictedStateChanged(CTCellularDataRestrictedState state) { ReachabilityChanged?.Invoke(); } async void OnChange(NetworkReachabilityFlags flags) { // Add in artificial delay so the connection status has time to change // else it will return true no matter what. await Task.Delay(100); ReachabilityChanged?.Invoke(); } } }