//
// System.Net.NetworkInformation.NetworkChange
//
-// Author:
-// Gonzalo Paniagua Javier (gonzalo@novell.com)
+// Authors:
+// Gonzalo Paniagua Javier (LinuxNetworkChange) (gonzalo@novell.com)
+// Aaron Bockover (MacNetworkChange) (abock@xamarin.com)
//
-// Copyright (c) 2006 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2006,2011 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2013 Xamarin, Inc. (http://www.xamarin.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
-//
+//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
-//
+//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
-#if NET_2_0
+
+using System;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+#if NETWORK_CHANGE_STANDALONE
+namespace NetworkInformation {
+
+ public class NetworkAvailabilityEventArgs : EventArgs
+ {
+ public bool IsAvailable { get; set; }
+
+ public NetworkAvailabilityEventArgs (bool available)
+ {
+ IsAvailable = available;
+ }
+ }
+
+ public delegate void NetworkAddressChangedEventHandler (object sender, EventArgs args);
+ public delegate void NetworkAvailabilityChangedEventHandler (object sender, NetworkAvailabilityEventArgs args);
+#else
namespace System.Net.NetworkInformation {
+#endif
+
+ internal interface INetworkChange : IDisposable {
+ event NetworkAddressChangedEventHandler NetworkAddressChanged;
+ event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
+ bool HasRegisteredEvents { get; }
+ }
+
public sealed class NetworkChange {
- private NetworkChange ()
+ static INetworkChange networkChange;
+
+ public static event NetworkAddressChangedEventHandler NetworkAddressChanged {
+ add {
+ lock (typeof (INetworkChange)) {
+ MaybeCreate ();
+ if (networkChange != null)
+ networkChange.NetworkAddressChanged += value;
+ }
+ }
+
+ remove {
+ lock (typeof (INetworkChange)) {
+ if (networkChange != null) {
+ networkChange.NetworkAddressChanged -= value;
+ MaybeDispose ();
+ }
+ }
+ }
+ }
+
+ public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
+ add {
+ lock (typeof (INetworkChange)) {
+ MaybeCreate ();
+ if (networkChange != null)
+ networkChange.NetworkAvailabilityChanged += value;
+ }
+ }
+
+ remove {
+ lock (typeof (INetworkChange)) {
+ if (networkChange != null) {
+ networkChange.NetworkAvailabilityChanged -= value;
+ MaybeDispose ();
+ }
+ }
+ }
+ }
+
+ static void MaybeCreate ()
{
+ if (networkChange != null)
+ return;
+
+ try {
+ networkChange = new MacNetworkChange ();
+ } catch {
+#if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
+ networkChange = new LinuxNetworkChange ();
+#endif
+ }
}
- // Disable the warnings about the events not being used.
-#pragma warning disable 67
- public static event NetworkAddressChangedEventHandler NetworkAddressChanged;
- public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
-#pragma warning restore
+ static void MaybeDispose ()
+ {
+ if (networkChange != null && networkChange.HasRegisteredEvents) {
+ networkChange.Dispose ();
+ networkChange = null;
+ }
+ }
}
-}
+
+ internal sealed class MacNetworkChange : INetworkChange
+ {
+ const string DL_LIB = "/usr/lib/libSystem.dylib";
+ const string CORE_SERVICES_LIB = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
+ const string CORE_FOUNDATION_LIB = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
+
+ [UnmanagedFunctionPointerAttribute (CallingConvention.Cdecl)]
+ delegate void SCNetworkReachabilityCallback (IntPtr target, NetworkReachabilityFlags flags, IntPtr info);
+
+ [DllImport (DL_LIB)]
+ static extern IntPtr dlopen (string path, int mode);
+
+ [DllImport (DL_LIB)]
+ static extern IntPtr dlsym (IntPtr handle, string symbol);
+
+ [DllImport (DL_LIB)]
+ static extern int dlclose (IntPtr handle);
+
+ [DllImport (CORE_FOUNDATION_LIB)]
+ static extern void CFRelease (IntPtr handle);
+
+ [DllImport (CORE_FOUNDATION_LIB)]
+ static extern IntPtr CFRunLoopGetMain ();
+
+ [DllImport (CORE_SERVICES_LIB)]
+ static extern IntPtr SCNetworkReachabilityCreateWithAddress (IntPtr allocator, ref sockaddr_in sockaddr);
+
+ [DllImport (CORE_SERVICES_LIB)]
+ static extern bool SCNetworkReachabilityGetFlags (IntPtr reachability, out NetworkReachabilityFlags flags);
+
+ [DllImport (CORE_SERVICES_LIB)]
+ static extern bool SCNetworkReachabilitySetCallback (IntPtr reachability, SCNetworkReachabilityCallback callback, ref SCNetworkReachabilityContext context);
+
+ [DllImport (CORE_SERVICES_LIB)]
+ static extern bool SCNetworkReachabilityScheduleWithRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
+
+ [DllImport (CORE_SERVICES_LIB)]
+ static extern bool SCNetworkReachabilityUnscheduleFromRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
+
+ [StructLayout (LayoutKind.Explicit, Size = 28)]
+ struct sockaddr_in {
+ [FieldOffset (0)] public byte sin_len;
+ [FieldOffset (1)] public byte sin_family;
+
+ public static sockaddr_in Create ()
+ {
+ return new sockaddr_in {
+ sin_len = 28,
+ sin_family = 2 // AF_INET
+ };
+ }
+ }
+
+ [StructLayout (LayoutKind.Sequential)]
+ struct SCNetworkReachabilityContext {
+ public IntPtr version;
+ public IntPtr info;
+ public IntPtr retain;
+ public IntPtr release;
+ public IntPtr copyDescription;
+ }
+
+ [Flags]
+ enum NetworkReachabilityFlags {
+ None = 0,
+ TransientConnection = 1 << 0,
+ Reachable = 1 << 1,
+ ConnectionRequired = 1 << 2,
+ ConnectionOnTraffic = 1 << 3,
+ InterventionRequired = 1 << 4,
+ ConnectionOnDemand = 1 << 5,
+ IsLocalAddress = 1 << 16,
+ IsDirect = 1 << 17,
+ IsWWAN = 1 << 18,
+ ConnectionAutomatic = ConnectionOnTraffic
+ }
+
+ IntPtr handle;
+ IntPtr runLoopMode;
+ SCNetworkReachabilityCallback callback;
+ bool scheduledWithRunLoop;
+ NetworkReachabilityFlags flags;
+
+ event NetworkAddressChangedEventHandler networkAddressChanged;
+ event NetworkAvailabilityChangedEventHandler networkAvailabilityChanged;
+
+ public event NetworkAddressChangedEventHandler NetworkAddressChanged {
+ add {
+ value (null, EventArgs.Empty);
+ networkAddressChanged += value;
+ }
+
+ remove { networkAddressChanged -= value; }
+ }
+
+ public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
+ add {
+ value (null, new NetworkAvailabilityEventArgs (IsAvailable));
+ networkAvailabilityChanged += value;
+ }
+
+ remove { networkAvailabilityChanged -= value; }
+ }
+
+ bool IsAvailable {
+ get {
+ return (flags & NetworkReachabilityFlags.Reachable) != 0 &&
+ (flags & NetworkReachabilityFlags.ConnectionRequired) == 0;
+ }
+ }
+
+ public bool HasRegisteredEvents {
+ get { return networkAddressChanged != null || networkAvailabilityChanged != null; }
+ }
+
+ public MacNetworkChange ()
+ {
+ var sockaddr = sockaddr_in.Create ();
+ handle = SCNetworkReachabilityCreateWithAddress (IntPtr.Zero, ref sockaddr);
+ if (handle == IntPtr.Zero)
+ throw new Exception ("SCNetworkReachabilityCreateWithAddress returned NULL");
+
+ callback = new SCNetworkReachabilityCallback (HandleCallback);
+ var info = new SCNetworkReachabilityContext {
+ info = GCHandle.ToIntPtr (GCHandle.Alloc (this))
+ };
+
+ SCNetworkReachabilitySetCallback (handle, callback, ref info);
+
+ scheduledWithRunLoop =
+ LoadRunLoopMode () &&
+ SCNetworkReachabilityScheduleWithRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
+
+ SCNetworkReachabilityGetFlags (handle, out flags);
+ }
+
+ bool LoadRunLoopMode ()
+ {
+ var cfLibHandle = dlopen (CORE_FOUNDATION_LIB, 0);
+ if (cfLibHandle == IntPtr.Zero)
+ return false;
+
+ try {
+ runLoopMode = dlsym (cfLibHandle, "kCFRunLoopDefaultMode");
+ if (runLoopMode != IntPtr.Zero) {
+ runLoopMode = Marshal.ReadIntPtr (runLoopMode);
+ return runLoopMode != IntPtr.Zero;
+ }
+ } finally {
+ dlclose (cfLibHandle);
+ }
+
+ return false;
+ }
+
+ public void Dispose ()
+ {
+ lock (this) {
+ if (handle == IntPtr.Zero)
+ return;
+
+ if (scheduledWithRunLoop)
+ SCNetworkReachabilityUnscheduleFromRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
+
+ CFRelease (handle);
+ handle = IntPtr.Zero;
+ callback = null;
+ flags = NetworkReachabilityFlags.None;
+ scheduledWithRunLoop = false;
+ }
+ }
+
+#if MONOTOUCH
+ [MonoTouch.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
+#endif
+ static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
+ {
+ if (info == IntPtr.Zero)
+ return;
+
+ var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
+ if (instance == null || instance.flags == flags)
+ return;
+
+ instance.flags = flags;
+
+ var addressChanged = instance.networkAddressChanged;
+ if (addressChanged != null)
+ addressChanged (null, EventArgs.Empty);
+
+ var availabilityChanged = instance.networkAvailabilityChanged;
+ if (availabilityChanged != null)
+ availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
+ }
+ }
+
+#if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
+
+ internal sealed class LinuxNetworkChange : INetworkChange {
+ [Flags]
+ enum EventType {
+ Availability = 1 << 0,
+ Address = 1 << 1,
+ }
+
+ object _lock = new object ();
+ Socket nl_sock;
+ SocketAsyncEventArgs nl_args;
+ EventType pending_events;
+ Timer timer;
+
+ NetworkAddressChangedEventHandler AddressChanged;
+ NetworkAvailabilityChangedEventHandler AvailabilityChanged;
+
+ public event NetworkAddressChangedEventHandler NetworkAddressChanged {
+ add { Register (value); }
+ remove { Unregister (value); }
+ }
+
+ public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
+ add { Register (value); }
+ remove { Unregister (value); }
+ }
+
+ public bool HasRegisteredEvents {
+ get { return AddressChanged != null || AvailabilityChanged != null; }
+ }
+
+ public void Dispose ()
+ {
+ }
+
+ //internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
+
+ bool EnsureSocket ()
+ {
+ lock (_lock) {
+ if (nl_sock != null)
+ return true;
+ IntPtr fd = CreateNLSocket ();
+ if (fd.ToInt64 () == -1)
+ return false;
+
+ nl_sock = new Socket (0, SocketType.Raw, ProtocolType.Udp, fd);
+ nl_args = new SocketAsyncEventArgs ();
+ nl_args.SetBuffer (new byte [8192], 0, 8192);
+ nl_args.Completed += OnDataAvailable;
+ nl_sock.ReceiveAsync (nl_args);
+ }
+ return true;
+ }
+
+ // _lock is held by the caller
+ void MaybeCloseSocket ()
+ {
+ if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
+ return;
+
+ CloseNLSocket (nl_sock.Handle);
+ GC.SuppressFinalize (nl_sock);
+ nl_sock = null;
+ nl_args = null;
+ }
+
+ bool GetAvailability ()
+ {
+ NetworkInterface [] adapters = NetworkInterface.GetAllNetworkInterfaces ();
+ foreach (NetworkInterface n in adapters) {
+ // TODO: also check for a default route present?
+ if (n.NetworkInterfaceType == NetworkInterfaceType.Loopback)
+ continue;
+ if (n.OperationalStatus == OperationalStatus.Up)
+ return true;
+ }
+ return false;
+ }
+
+ void OnAvailabilityChanged (object unused)
+ {
+ NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
+ d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
+ }
+
+ void OnAddressChanged (object unused)
+ {
+ NetworkAddressChangedEventHandler d = AddressChanged;
+ d (null, EventArgs.Empty);
+ }
+
+ void OnEventDue (object unused)
+ {
+ EventType evts;
+ lock (_lock) {
+ evts = pending_events;
+ pending_events = 0;
+ timer.Change (-1, -1);
+ }
+ if ((evts & EventType.Availability) != 0)
+ ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
+ if ((evts & EventType.Address) != 0)
+ ThreadPool.QueueUserWorkItem (OnAddressChanged);
+ }
+
+ void QueueEvent (EventType type)
+ {
+ lock (_lock) {
+ if (timer == null)
+ timer = new Timer (OnEventDue);
+ if (pending_events == 0)
+ timer.Change (150, -1);
+ pending_events |= type;
+ }
+ }
+
+ unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
+ {
+ EventType type;
+ fixed (byte *ptr = args.Buffer) {
+ type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
+ }
+ nl_sock.ReceiveAsync (nl_args);
+ if (type != 0)
+ QueueEvent (type);
+ }
+
+ void Register (NetworkAddressChangedEventHandler d)
+ {
+ EnsureSocket ();
+ AddressChanged += d;
+ }
+
+ void Register (NetworkAvailabilityChangedEventHandler d)
+ {
+ EnsureSocket ();
+ AvailabilityChanged += d;
+ }
+
+ void Unregister (NetworkAddressChangedEventHandler d)
+ {
+ lock (_lock) {
+ AddressChanged -= d;
+ MaybeCloseSocket ();
+ }
+ }
+
+ void Unregister (NetworkAvailabilityChangedEventHandler d)
+ {
+ lock (_lock) {
+ AvailabilityChanged -= d;
+ MaybeCloseSocket ();
+ }
+ }
+
+#if MONOTOUCH || MONODROID
+ const string LIBNAME = "__Internal";
+#else
+ const string LIBNAME = "MonoPosixHelper";
#endif
+ [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
+ static extern IntPtr CreateNLSocket ();
+
+ [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
+ static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
+
+ [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
+ static extern IntPtr CloseNLSocket (IntPtr sock);
+ }
+
+#endif
+
+}