2 // System.Net.NetworkInformation.NetworkChange
5 // Gonzalo Paniagua Javier (LinuxNetworkChange) (gonzalo@novell.com)
6 // Aaron Bockover (MacNetworkChange) (abock@xamarin.com)
8 // Copyright (c) 2006,2011 Novell, Inc. (http://www.novell.com)
9 // Copyright (c) 2013 Xamarin, Inc. (http://www.xamarin.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Net.Sockets;
33 using System.Runtime.InteropServices;
34 using System.Threading;
36 #if NETWORK_CHANGE_STANDALONE
37 namespace NetworkInformation {
39 public class NetworkAvailabilityEventArgs : EventArgs
41 public bool IsAvailable { get; set; }
43 public NetworkAvailabilityEventArgs (bool available)
45 IsAvailable = available;
49 public delegate void NetworkAddressChangedEventHandler (object sender, EventArgs args);
50 public delegate void NetworkAvailabilityChangedEventHandler (object sender, NetworkAvailabilityEventArgs args);
52 namespace System.Net.NetworkInformation {
55 internal interface INetworkChange : IDisposable {
56 event NetworkAddressChangedEventHandler NetworkAddressChanged;
57 event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
58 bool HasRegisteredEvents { get; }
61 public sealed class NetworkChange {
62 static INetworkChange networkChange;
64 public static event NetworkAddressChangedEventHandler NetworkAddressChanged {
66 lock (typeof (INetworkChange)) {
68 if (networkChange != null)
69 networkChange.NetworkAddressChanged += value;
74 lock (typeof (INetworkChange)) {
75 if (networkChange != null) {
76 networkChange.NetworkAddressChanged -= value;
83 public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
85 lock (typeof (INetworkChange)) {
87 if (networkChange != null)
88 networkChange.NetworkAvailabilityChanged += value;
93 lock (typeof (INetworkChange)) {
94 if (networkChange != null) {
95 networkChange.NetworkAvailabilityChanged -= value;
102 static void MaybeCreate ()
104 #if MONOTOUCH_WATCH || ORBIS
105 throw new PlatformNotSupportedException ("NetworkInformation.NetworkChange is not supported on the current platform.");
107 if (networkChange != null)
111 networkChange = new MacNetworkChange ();
113 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
114 networkChange = new LinuxNetworkChange ();
117 #endif // MONOTOUCH_WATCH
120 static void MaybeDispose ()
122 if (networkChange != null && networkChange.HasRegisteredEvents) {
123 networkChange.Dispose ();
124 networkChange = null;
129 #if !MONOTOUCH_WATCH && !ORBIS
130 internal sealed class MacNetworkChange : INetworkChange
132 const string DL_LIB = "/usr/lib/libSystem.dylib";
133 const string CORE_SERVICES_LIB = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
134 const string CORE_FOUNDATION_LIB = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
136 [UnmanagedFunctionPointerAttribute (CallingConvention.Cdecl)]
137 delegate void SCNetworkReachabilityCallback (IntPtr target, NetworkReachabilityFlags flags, IntPtr info);
140 static extern IntPtr dlopen (string path, int mode);
143 static extern IntPtr dlsym (IntPtr handle, string symbol);
146 static extern int dlclose (IntPtr handle);
148 [DllImport (CORE_FOUNDATION_LIB)]
149 static extern void CFRelease (IntPtr handle);
151 [DllImport (CORE_FOUNDATION_LIB)]
152 static extern IntPtr CFRunLoopGetMain ();
154 [DllImport (CORE_SERVICES_LIB)]
155 static extern IntPtr SCNetworkReachabilityCreateWithAddress (IntPtr allocator, ref sockaddr_in sockaddr);
157 [DllImport (CORE_SERVICES_LIB)]
158 static extern bool SCNetworkReachabilityGetFlags (IntPtr reachability, out NetworkReachabilityFlags flags);
160 [DllImport (CORE_SERVICES_LIB)]
161 static extern bool SCNetworkReachabilitySetCallback (IntPtr reachability, SCNetworkReachabilityCallback callback, ref SCNetworkReachabilityContext context);
163 [DllImport (CORE_SERVICES_LIB)]
164 static extern bool SCNetworkReachabilityScheduleWithRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
166 [DllImport (CORE_SERVICES_LIB)]
167 static extern bool SCNetworkReachabilityUnscheduleFromRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
169 [StructLayout (LayoutKind.Explicit, Size = 28)]
171 [FieldOffset (0)] public byte sin_len;
172 [FieldOffset (1)] public byte sin_family;
174 public static sockaddr_in Create ()
176 return new sockaddr_in {
178 sin_family = 2 // AF_INET
183 [StructLayout (LayoutKind.Sequential)]
184 struct SCNetworkReachabilityContext {
185 public IntPtr version;
187 public IntPtr retain;
188 public IntPtr release;
189 public IntPtr copyDescription;
193 enum NetworkReachabilityFlags {
195 TransientConnection = 1 << 0,
197 ConnectionRequired = 1 << 2,
198 ConnectionOnTraffic = 1 << 3,
199 InterventionRequired = 1 << 4,
200 ConnectionOnDemand = 1 << 5,
201 IsLocalAddress = 1 << 16,
204 ConnectionAutomatic = ConnectionOnTraffic
209 SCNetworkReachabilityCallback callback;
210 bool scheduledWithRunLoop;
211 NetworkReachabilityFlags flags;
213 event NetworkAddressChangedEventHandler networkAddressChanged;
214 event NetworkAvailabilityChangedEventHandler networkAvailabilityChanged;
216 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
218 value (null, EventArgs.Empty);
219 networkAddressChanged += value;
222 remove { networkAddressChanged -= value; }
225 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
227 value (null, new NetworkAvailabilityEventArgs (IsAvailable));
228 networkAvailabilityChanged += value;
231 remove { networkAvailabilityChanged -= value; }
236 return (flags & NetworkReachabilityFlags.Reachable) != 0 &&
237 (flags & NetworkReachabilityFlags.ConnectionRequired) == 0;
241 public bool HasRegisteredEvents {
242 get { return networkAddressChanged != null || networkAvailabilityChanged != null; }
245 public MacNetworkChange ()
247 var sockaddr = sockaddr_in.Create ();
248 handle = SCNetworkReachabilityCreateWithAddress (IntPtr.Zero, ref sockaddr);
249 if (handle == IntPtr.Zero)
250 throw new Exception ("SCNetworkReachabilityCreateWithAddress returned NULL");
252 callback = new SCNetworkReachabilityCallback (HandleCallback);
253 var info = new SCNetworkReachabilityContext {
254 info = GCHandle.ToIntPtr (GCHandle.Alloc (this))
257 SCNetworkReachabilitySetCallback (handle, callback, ref info);
259 scheduledWithRunLoop =
260 LoadRunLoopMode () &&
261 SCNetworkReachabilityScheduleWithRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
263 SCNetworkReachabilityGetFlags (handle, out flags);
266 bool LoadRunLoopMode ()
268 var cfLibHandle = dlopen (CORE_FOUNDATION_LIB, 0);
269 if (cfLibHandle == IntPtr.Zero)
273 runLoopMode = dlsym (cfLibHandle, "kCFRunLoopDefaultMode");
274 if (runLoopMode != IntPtr.Zero) {
275 runLoopMode = Marshal.ReadIntPtr (runLoopMode);
276 return runLoopMode != IntPtr.Zero;
279 dlclose (cfLibHandle);
285 public void Dispose ()
288 if (handle == IntPtr.Zero)
291 if (scheduledWithRunLoop)
292 SCNetworkReachabilityUnscheduleFromRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
295 handle = IntPtr.Zero;
297 flags = NetworkReachabilityFlags.None;
298 scheduledWithRunLoop = false;
302 [Mono.Util.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
303 static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
305 if (info == IntPtr.Zero)
308 var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
309 if (instance == null || instance.flags == flags)
312 instance.flags = flags;
314 var addressChanged = instance.networkAddressChanged;
315 if (addressChanged != null)
316 addressChanged (null, EventArgs.Empty);
318 var availabilityChanged = instance.networkAvailabilityChanged;
319 if (availabilityChanged != null)
320 availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
323 #endif // !MONOTOUCH_WATCH
325 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH && !ORBIS
327 internal sealed class LinuxNetworkChange : INetworkChange {
330 Availability = 1 << 0,
334 object _lock = new object ();
336 SocketAsyncEventArgs nl_args;
337 EventType pending_events;
340 NetworkAddressChangedEventHandler AddressChanged;
341 NetworkAvailabilityChangedEventHandler AvailabilityChanged;
343 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
344 add { Register (value); }
345 remove { Unregister (value); }
348 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
349 add { Register (value); }
350 remove { Unregister (value); }
353 public bool HasRegisteredEvents {
354 get { return AddressChanged != null || AvailabilityChanged != null; }
357 public void Dispose ()
361 //internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
368 IntPtr fd = CreateNLSocket ();
369 if (fd.ToInt64 () == -1)
372 var safeHandle = new SafeSocketHandle (fd, true);
374 nl_sock = new Socket (0, SocketType.Raw, ProtocolType.Udp, safeHandle);
375 nl_args = new SocketAsyncEventArgs ();
376 nl_args.SetBuffer (new byte [8192], 0, 8192);
377 nl_args.Completed += OnDataAvailable;
378 nl_sock.ReceiveAsync (nl_args);
383 // _lock is held by the caller
384 void MaybeCloseSocket ()
386 if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
389 CloseNLSocket (nl_sock.Handle);
390 GC.SuppressFinalize (nl_sock);
395 bool GetAvailability ()
397 NetworkInterface [] adapters = NetworkInterface.GetAllNetworkInterfaces ();
398 foreach (NetworkInterface n in adapters) {
399 // TODO: also check for a default route present?
400 if (n.NetworkInterfaceType == NetworkInterfaceType.Loopback)
402 if (n.OperationalStatus == OperationalStatus.Up)
408 void OnAvailabilityChanged (object unused)
410 NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
412 d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
415 void OnAddressChanged (object unused)
417 NetworkAddressChangedEventHandler d = AddressChanged;
419 d (null, EventArgs.Empty);
422 void OnEventDue (object unused)
426 evts = pending_events;
428 timer.Change (-1, -1);
430 if ((evts & EventType.Availability) != 0)
431 ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
432 if ((evts & EventType.Address) != 0)
433 ThreadPool.QueueUserWorkItem (OnAddressChanged);
436 void QueueEvent (EventType type)
440 timer = new Timer (OnEventDue);
441 if (pending_events == 0)
442 timer.Change (150, -1);
443 pending_events |= type;
447 unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
449 if (nl_sock == null) // Recent changes in Mono cause MaybeCloseSocket to be called before OnDataAvailable
452 fixed (byte *ptr = args.Buffer) {
453 type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
455 nl_sock.ReceiveAsync (nl_args);
460 void Register (NetworkAddressChangedEventHandler d)
466 void Register (NetworkAvailabilityChangedEventHandler d)
469 AvailabilityChanged += d;
472 void Unregister (NetworkAddressChangedEventHandler d)
480 void Unregister (NetworkAvailabilityChangedEventHandler d)
483 AvailabilityChanged -= d;
488 #if MONOTOUCH || MONODROID
489 const string LIBNAME = "__Internal";
491 const string LIBNAME = "MonoPosixHelper";
494 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
495 static extern IntPtr CreateNLSocket ();
497 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
498 static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
500 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
501 static extern IntPtr CloseNLSocket (IntPtr sock);