Permalink
Browse files

NetworkChange: Support Mac OS X (bcx#12038)

Refactor System.Net.NetworkingInformation in order
to support multiple "providers"; previously only
Linux was ever supported for this API.

This patch provides basic support for Mac OS X
via the SystemConfiguration framework's
SCNetworkReachability API.

bxc#12038
  • Loading branch information...
1 parent db8f337 commit ed5d39b1d76b82c305172aaa86875227bd2a370c @abock abock committed with abock May 1, 2013
Showing with 303 additions and 26 deletions.
  1. +150 −26 mcs/class/System/System.Net.NetworkInformation/NetworkChange.cs
  2. +5 −0 support/Makefile.am
  3. +148 −0 support/mac-reachability.c
View
176 mcs/class/System/System.Net.NetworkInformation/NetworkChange.cs
@@ -1,10 +1,12 @@
//
// System.Net.NetworkInformation.NetworkChange
//
-// Author:
+// Authors:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
+// Aaron Bockover (abock@xamarin.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
@@ -31,39 +33,161 @@
using System.Threading;
namespace System.Net.NetworkInformation {
+ internal interface INetworkChange {
+ event NetworkAddressChangedEventHandler NetworkAddressChanged;
+ event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
+ }
+
public sealed class NetworkChange {
+ static INetworkChange networkChange;
+
+ static NetworkChange ()
+ {
+ if (MacNetworkChange.IsEnabled) {
+ networkChange = new MacNetworkChange ();
+ } else {
+ networkChange = new LinuxNetworkChange ();
+ }
+ }
+
+ public static event NetworkAddressChangedEventHandler NetworkAddressChanged {
+ add { networkChange.NetworkAddressChanged += value; }
+ remove { networkChange.NetworkAddressChanged -= value; }
+ }
+
+ public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
+ add { networkChange.NetworkAvailabilityChanged += value; }
+ remove { networkChange.NetworkAvailabilityChanged -= value; }
+ }
+ }
+
+ internal sealed class MacNetworkChange : INetworkChange {
+ public static bool IsEnabled {
+ get { return mono_sc_reachability_enabled () != 0; }
+ }
+
+ event NetworkAddressChangedEventHandler networkAddressChanged;
+ event NetworkAvailabilityChangedEventHandler networkAvailabilityChanged;
+
+ public event NetworkAddressChangedEventHandler NetworkAddressChanged {
+ add {
+ if (value != null) {
+ MaybeInitialize ();
+ networkAddressChanged += value;
+ value (null, EventArgs.Empty);
+ }
+ }
+
+ remove {
+ networkAddressChanged -= value;
+ MaybeDispose ();
+ }
+ }
+
+ 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));
+ }
+ }
+
+ remove {
+ networkAvailabilityChanged -= value;
+ MaybeDispose ();
+ }
+ }
+
+ IntPtr handle;
+ MonoSCReachabilityCallback callback;
+
+ void Callback (int available)
+ {
+ var addressChanged = networkAddressChanged;
+ if (addressChanged != null) {
+ addressChanged (null, EventArgs.Empty);
+ }
+
+ var availabilityChanged = networkAvailabilityChanged;
+ if (availabilityChanged != null) {
+ availabilityChanged (null, new NetworkAvailabilityEventArgs (available != 0));
+ }
+ }
+
+ void MaybeInitialize ()
+ {
+ lock (this) {
+ if (handle == IntPtr.Zero) {
+ callback = new MonoSCReachabilityCallback (Callback);
+ handle = mono_sc_reachability_new (callback);
+ }
+ }
+ }
+
+ void MaybeDispose ()
+ {
+ 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 MONOTOUCH || MONODROID
+ const string LIBNAME = "__Internal";
+#else
+ const string LIBNAME = "MonoPosixHelper";
+#endif
+
+ delegate void MonoSCReachabilityCallback (int available);
+
+ [DllImport (LIBNAME)]
+ static extern int mono_sc_reachability_enabled ();
+
+ [DllImport (LIBNAME)]
+ static extern IntPtr mono_sc_reachability_new (MonoSCReachabilityCallback callback);
+
+ [DllImport (LIBNAME)]
+ static extern void mono_sc_reachability_free (IntPtr handle);
+
+ [DllImport (LIBNAME)]
+ static extern int mono_sc_reachability_is_available (IntPtr handle);
+ }
+
+ internal sealed class LinuxNetworkChange : INetworkChange {
[Flags]
enum EventType {
Availability = 1 << 0,
Address = 1 << 1,
}
- static object _lock = new object ();
- static Socket nl_sock;
- static SocketAsyncEventArgs nl_args;
- static EventType pending_events;
- static Timer timer;
+ object _lock = new object ();
+ Socket nl_sock;
+ SocketAsyncEventArgs nl_args;
+ EventType pending_events;
+ Timer timer;
- static NetworkAddressChangedEventHandler AddressChanged;
- static NetworkAvailabilityChangedEventHandler AvailabilityChanged;
+ NetworkAddressChangedEventHandler AddressChanged;
+ NetworkAvailabilityChangedEventHandler AvailabilityChanged;
- private NetworkChange ()
- {
- }
-
- public static event NetworkAddressChangedEventHandler NetworkAddressChanged {
+ public event NetworkAddressChangedEventHandler NetworkAddressChanged {
add { Register (value); }
remove { Unregister (value); }
}
- public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
+ public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
add { Register (value); }
remove { Unregister (value); }
}
//internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
- static bool EnsureSocket ()
+ bool EnsureSocket ()
{
lock (_lock) {
if (nl_sock != null)
@@ -82,7 +206,7 @@ static bool EnsureSocket ()
}
// _lock is held by the caller
- static void MaybeCloseSocket ()
+ void MaybeCloseSocket ()
{
if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
return;
@@ -93,7 +217,7 @@ static void MaybeCloseSocket ()
nl_args = null;
}
- static bool GetAvailability ()
+ bool GetAvailability ()
{
NetworkInterface [] adapters = NetworkInterface.GetAllNetworkInterfaces ();
foreach (NetworkInterface n in adapters) {
@@ -106,19 +230,19 @@ static bool GetAvailability ()
return false;
}
- static void OnAvailabilityChanged (object unused)
+ void OnAvailabilityChanged (object unused)
{
NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
}
- static void OnAddressChanged (object unused)
+ void OnAddressChanged (object unused)
{
NetworkAddressChangedEventHandler d = AddressChanged;
d (null, EventArgs.Empty);
}
- static void OnEventDue (object unused)
+ void OnEventDue (object unused)
{
EventType evts;
lock (_lock) {
@@ -132,7 +256,7 @@ static void OnEventDue (object unused)
ThreadPool.QueueUserWorkItem (OnAddressChanged);
}
- static void QueueEvent (EventType type)
+ void QueueEvent (EventType type)
{
lock (_lock) {
if (timer == null)
@@ -143,7 +267,7 @@ static void QueueEvent (EventType type)
}
}
- unsafe static void OnDataAvailable (object sender, SocketAsyncEventArgs args)
+ unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
{
EventType type;
fixed (byte *ptr = args.Buffer) {
@@ -154,27 +278,27 @@ unsafe static void OnDataAvailable (object sender, SocketAsyncEventArgs args)
QueueEvent (type);
}
- static void Register (NetworkAddressChangedEventHandler d)
+ void Register (NetworkAddressChangedEventHandler d)
{
EnsureSocket ();
AddressChanged += d;
}
- static void Register (NetworkAvailabilityChangedEventHandler d)
+ void Register (NetworkAvailabilityChangedEventHandler d)
{
EnsureSocket ();
AvailabilityChanged += d;
}
- static void Unregister (NetworkAddressChangedEventHandler d)
+ void Unregister (NetworkAddressChangedEventHandler d)
{
lock (_lock) {
AddressChanged -= d;
MaybeCloseSocket ();
}
}
- static void Unregister (NetworkAvailabilityChangedEventHandler d)
+ void Unregister (NetworkAvailabilityChangedEventHandler d)
{
lock (_lock) {
AvailabilityChanged -= d;
View
5 support/Makefile.am
@@ -32,6 +32,7 @@ MPH_UNIX_SOURCE = \
fstab.c \
grp.c \
macros.c \
+ mac-reachability.c \
nl.c \
nl.h \
old-map.c \
@@ -115,6 +116,10 @@ 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 \
View
148 support/mac-reachability.c
@@ -0,0 +1,148 @@
+//
+// mac-reachability.c: System.Net.NetworkingInformation.NetworkChange
+// implementation for Mac OS X using SystemConfiguration's
+// NetworkReachability API.
+//
+// Authors:
+// Aaron Bockover (abock@xamarin.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
+// "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.
+//
+
+int mono_sc_reachability_enabled ();
+
+#ifdef PLATFORM_MACOSX
+
+int
+mono_sc_reachability_enabled ()
+{
+ 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 = {0,};
+ 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;
+
+ 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 ()
+{
+ return 0;
+}
+
+#endif

0 comments on commit ed5d39b

Please sign in to comment.