[mono/mono] d0756232: bxc#13642: MacNetworkChange: implement using managed code

"Aaron Bockover ([email protected])" <[email protected]>
Newsgroups gmane.comp.gnome.mono.patches
Message-ID <00000141a34ee4a7-060b4e4a-e203-40c1-a3bf-ec174e8beb57-000000@email.amazonses.com>
   Branch: refs/heads/master
     Home: https://github.com/mono/mono
  Compare: https://github.com/mono/mono/compare/a55758d13614...d07562327520

   Commit: d0756232752057ddf80046f09bef451749d93add
   Author: Aaron Bockover <[email protected]> (abock)
     Date: 2013-10-10 16:35:47 GMT
      URL: https://github.com/mono/mono/commit/d0756232752057ddf80046f09bef451749d93add

bxc#13642: MacNetworkChange: implement using managed code

The previous version used SCNetworkReachability
in C inside libMonoPosixHelper. This was less
than ideal and made it more problematic for
supporting iOS as it doesn't actually link
MPH in.

Rewrite using P/Invokes directly into the
SCNetworkReachability APIs for Mac OS X and
iOS.

Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=13642
for iOS and Mac OS X.

Changed paths:
  M mcs/class/System/System.Net.NetworkInformation/NetworkChange.cs
  M support/Makefile.am
Removed paths:
  D support/mac-reachability.c

Modified: mcs/class/System/System.Net.NetworkInformation/NetworkChange.cs
===================================================================
@@ -2,8 +2,8 @@
 // System.Net.NetworkInformation.NetworkChange
 //
 // Authors:
-//	Gonzalo Paniagua Javier ([email protected])
-//  Aaron Bockover ([email protected])
+//   Gonzalo Paniagua Javier (LinuxNetworkChange) ([email protected])
+//   Aaron Bockover (MacNetworkChange) ([email protected])
 //
 // Copyright (c) 2006,2011 Novell, Inc. (http://www.novell.com)
 // Copyright (c) 2013 Xamarin, Inc. (http://www.xamarin.com)
@@ -15,10 +15,10 @@
 // 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
@@ -28,137 +28,298 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
+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 {
-	internal interface INetworkChange {
+#endif
+
+	internal interface INetworkChange : IDisposable {
 		event NetworkAddressChangedEventHandler NetworkAddressChanged;
 		event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
+		bool HasRegisteredEvents { get; }
 	}
 
 	public sealed class NetworkChange {
 		static INetworkChange networkChange;
 
-		static 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 (MacNetworkChange.IsEnabled) {
+			if (networkChange != null)
+				return;
+
+			try {
 				networkChange = new MacNetworkChange ();
-			} else {
+			} catch {
+#if !NETWORK_CHANGE_STANDALONE
 				networkChange = new LinuxNetworkChange ();
+#endif
 			}
 		}
 
-		public static event NetworkAddressChangedEventHandler NetworkAddressChanged {
-			add { networkChange.NetworkAddressChanged += value; }
-			remove { networkChange.NetworkAddressChanged -= value; }
+		static void MaybeDispose ()
+		{
+			if (networkChange != null && networkChange.HasRegisteredEvents) {
+				networkChange.Dispose ();
+				networkChange = null;
+			}
 		}
+	}
 
-		public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
-			add { networkChange.NetworkAvailabilityChanged += value; }
-			remove { networkChange.NetworkAvailabilityChanged -= value; }
+	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
+				};
+			}
 		}
-	}
 
-	internal sealed class MacNetworkChange : INetworkChange {
-		public static bool IsEnabled {
-			get { return mono_sc_reachability_enabled () != 0; }
+		[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 {
-				if (value != null) {
-					MaybeInitialize ();
-					networkAddressChanged += value;
-					value (null, EventArgs.Empty);
-				}
+				value (null, EventArgs.Empty);
+				networkAddressChanged += value;
 			}
 
-			remove {
-				networkAddressChanged -= value;
-				MaybeDispose ();
-			}
+			remove { networkAddressChanged -= value; }
 		}
 
 		public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
 			add {
-				if (value != null) {
-					MaybeInitialize ();
-					networkAvailabilityChanged += value;
-					var available = handle != IntPtr.Zero && mono_sc_reachability_is_available (handle) != 0;
-					value (null, new NetworkAvailabilityEventArgs (available));
-				}
+				value (null, new NetworkAvailabilityEventArgs (IsAvailable));
+				networkAvailabilityChanged += value;
 			}
 
-			remove {
-				networkAvailabilityChanged -= value;
-				MaybeDispose ();
+			remove { networkAvailabilityChanged -= value; }
+		}
+
+		bool IsAvailable {
+			get {
+				return (flags & NetworkReachabilityFlags.Reachable) != 0 &&
+					(flags & NetworkReachabilityFlags.ConnectionRequired) == 0;
 			}
 		}
 
-		IntPtr handle;
-		MonoSCReachabilityCallback callback;
+		public bool HasRegisteredEvents {
+			get { return networkAddressChanged != null || networkAvailabilityChanged != null; }
+		}
 
-		void Callback (int available)
+		public MacNetworkChange ()
 		{
-			var addressChanged = networkAddressChanged;
-			if (addressChanged != null) {
-				addressChanged (null, EventArgs.Empty);
-			}
+			var sockaddr = sockaddr_in.Create ();
+			handle = SCNetworkReachabilityCreateWithAddress (IntPtr.Zero, ref sockaddr);
+			if (handle == IntPtr.Zero)
+				throw new Exception ("SCNetworkReachabilityCreateWithAddress returned NULL");
 
-			var availabilityChanged = networkAvailabilityChanged;
-			if (availabilityChanged != null) {
-				availabilityChanged (null, new NetworkAvailabilityEventArgs (available != 0));
-			}
+			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);
 		}
 
-		void MaybeInitialize ()
+		bool LoadRunLoopMode ()
 		{
-			lock (this) {
-				if (handle == IntPtr.Zero) {
-					callback = new MonoSCReachabilityCallback (Callback);
-					handle = mono_sc_reachability_new (callback);
+			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;
 		}
 
-		void MaybeDispose ()
+		public void Dispose ()
 		{
 			lock (this) {
-				var addressChanged = networkAddressChanged;
-				var availabilityChanged = networkAvailabilityChanged;
-				if (handle != IntPtr.Zero && addressChanged == null && availabilityChanged == null) {
-					mono_sc_reachability_free (handle);
-					handle = IntPtr.Zero;
-				}
+				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 || MONODROID
-		const string LIBNAME = "__Internal";
-#else
-		const string LIBNAME = "MonoPosixHelper";
+#if MONOTOUCH
+		[MonoTouch.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
 #endif
+		static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
+		{
+			if (info == IntPtr.Zero)
+				return;
 
-		delegate void MonoSCReachabilityCallback (int available);
-
-		[DllImport (LIBNAME)]
-		static extern int mono_sc_reachability_enabled ();
+			var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
+			if (instance == null || instance.flags == flags)
+				return;
 
-		[DllImport (LIBNAME)]
-		static extern IntPtr mono_sc_reachability_new (MonoSCReachabilityCallback callback);
+			instance.flags = flags;
 
-		[DllImport (LIBNAME)]
-		static extern void mono_sc_reachability_free (IntPtr handle);
+			var addressChanged = instance.networkAddressChanged;
+			if (addressChanged != null)
+				addressChanged (null, EventArgs.Empty);
 
-		[DllImport (LIBNAME)]
-		static extern int mono_sc_reachability_is_available (IntPtr handle);
+			var availabilityChanged = instance.networkAvailabilityChanged;
+			if (availabilityChanged != null)
+				availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
+		}
 	}
 
+#if !NETWORK_CHANGE_STANDALONE
+
 	internal sealed class LinuxNetworkChange : INetworkChange {
 		[Flags]
 		enum EventType {
@@ -185,6 +346,14 @@ enum EventType {
 			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 ()
@@ -321,5 +490,7 @@ void Unregister (NetworkAvailabilityChangedEventHandler d)
 		[DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
 		static extern IntPtr CloseNLSocket (IntPtr sock);
 	}
-}
 
+#endif
+
+}

Modified: support/Makefile.am
===================================================================
@@ -32,7 +32,6 @@ MPH_UNIX_SOURCE =				\
 	fstab.c					\
 	grp.c					\
 	macros.c				\
-	mac-reachability.c			\
 	nl.c					\
 	nl.h					\
 	old-map.c				\
@@ -116,10 +115,6 @@ libMonoPosixHelper_la_LIBADD =			\
 libMonoPosixHelper_la_LDFLAGS = -no-undefined -avoid-version
 libMonoSupportW_la_LDFLAGS = -no-undefined -avoid-version
 
-if PLATFORM_DARWIN
-libMonoPosixHelper_la_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration
-endif
-
 libMonoSupportW_la_SOURCES =			\
 		supportw.c			\
 		support-heap.c			\

Removed: support/mac-reachability.c
===================================================================
@@ -1,153 +0,0 @@
-//
-// mac-reachability.c: System.Net.NetworkingInformation.NetworkChange
-// implementation for Mac OS X using SystemConfiguration's
-// NetworkReachability API.
-//
-// Authors:
-//  Aaron Bockover ([email protected])
-//
-// 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
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// 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
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// 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 HAVE_CONFIG_H
-#include "config.h"
-#endif
-
-int mono_sc_reachability_enabled (void);
-
-#if defined(PLATFORM_MACOSX) || defined(TARGET_IOS)
-
-int
-mono_sc_reachability_enabled (void)
-{
-	return 1;
-}
-
-#include <SystemConfiguration/SCNetworkReachability.h>
-#include <netinet/in.h>
-
-typedef void (*mono_sc_reachability_callback)(int);
-
-typedef struct {
-	SCNetworkReachabilityRef reachability;
-	mono_sc_reachability_callback callback;
-} mono_sc_reachability;
-
-mono_sc_reachability * mono_sc_reachability_new (mono_sc_reachability_callback callback);
-void mono_sc_reachability_free (mono_sc_reachability *reachability);
-int mono_sc_reachability_is_available (mono_sc_reachability *reachability);
-
-static int
-_mono_sc_reachability_is_available (SCNetworkReachabilityFlags flags)
-{
-	return (flags & kSCNetworkFlagsReachable) && (flags & kSCNetworkFlagsConnectionRequired) == 0;
-}
-
-static void
-_mono_sc_reachability_callback (SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void *user)
-{
-	mono_sc_reachability *reachability;
-	
-	if (user == NULL) {
-		return;
-	}
-
-	reachability = (mono_sc_reachability *)user;
-	if (reachability->callback == NULL) {
-		return;
-	}
-
-	reachability->callback (_mono_sc_reachability_is_available (flags));
-}
-
-mono_sc_reachability *
-mono_sc_reachability_new (mono_sc_reachability_callback callback)
-{
-	struct sockaddr_in zero;
-	SCNetworkReachabilityRef reachability;
-	SCNetworkReachabilityContext context;
-	mono_sc_reachability *instance;
-
-	if (callback == NULL) {
-		return NULL;
-	}
-
-	bzero (&zero, sizeof (zero));
-	zero.sin_len = sizeof (zero);
-	zero.sin_family = AF_INET;
-
-	reachability = SCNetworkReachabilityCreateWithAddress (NULL, (const struct sockaddr *)&zero);
-	if (reachability == NULL) {
-		return NULL;
-	}
-
-	instance = (mono_sc_reachability *)malloc (sizeof (mono_sc_reachability));
-	instance->reachability = reachability;
-	instance->callback = callback;
-
-	bzero (&context, sizeof (context));
-	context.info = instance;
-
-	if (!SCNetworkReachabilitySetCallback (reachability, _mono_sc_reachability_callback, &context) ||
-		!SCNetworkReachabilityScheduleWithRunLoop (reachability, CFRunLoopGetCurrent (), kCFRunLoopDefaultMode)) {
-		mono_sc_reachability_free (instance);
-		return NULL;
-	}
-
-	return instance;
-}
-
-void
-mono_sc_reachability_free (mono_sc_reachability *reachability)
-{
-	if (reachability != NULL) {
-		if (reachability->reachability != NULL) {
-			SCNetworkReachabilityUnscheduleFromRunLoop (reachability->reachability,
-				CFRunLoopGetCurrent (), kCFRunLoopDefaultMode);
-			CFRelease (reachability->reachability);
-			reachability->reachability = NULL;
-		}
-
-		reachability->callback = NULL;
-		free (reachability);
-		reachability = NULL;
-	}
-}
-
-int
-mono_sc_reachability_is_available (mono_sc_reachability *reachability)
-{
-	SCNetworkReachabilityFlags flags;
-	return reachability != NULL && reachability->reachability != NULL &&
-		SCNetworkReachabilityGetFlags (reachability->reachability, &flags) &&
-		_mono_sc_reachability_is_available (flags);
-}
-
-#else
-
-int
-mono_sc_reachability_enabled (void)
-{
-	return 0;
-}
-
-#endif


_______________________________________________
Mono-patches maillist  -  [email protected]
http://lists.ximian.com/mailman/listinfo/mono-patches
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.