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 (networkChange != null)
108 networkChange = new MacNetworkChange ();
110 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
111 networkChange = new LinuxNetworkChange ();
116 static void MaybeDispose ()
118 if (networkChange != null && networkChange.HasRegisteredEvents) {
119 networkChange.Dispose ();
120 networkChange = null;
125 internal sealed class MacNetworkChange : INetworkChange
127 const string DL_LIB = "/usr/lib/libSystem.dylib";
128 const string CORE_SERVICES_LIB = "/System/Library/Frameworks/SystemConfiguration.framework/SystemConfiguration";
129 const string CORE_FOUNDATION_LIB = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
131 [UnmanagedFunctionPointerAttribute (CallingConvention.Cdecl)]
132 delegate void SCNetworkReachabilityCallback (IntPtr target, NetworkReachabilityFlags flags, IntPtr info);
135 static extern IntPtr dlopen (string path, int mode);
138 static extern IntPtr dlsym (IntPtr handle, string symbol);
141 static extern int dlclose (IntPtr handle);
143 [DllImport (CORE_FOUNDATION_LIB)]
144 static extern void CFRelease (IntPtr handle);
146 [DllImport (CORE_FOUNDATION_LIB)]
147 static extern IntPtr CFRunLoopGetMain ();
149 [DllImport (CORE_SERVICES_LIB)]
150 static extern IntPtr SCNetworkReachabilityCreateWithAddress (IntPtr allocator, ref sockaddr_in sockaddr);
152 [DllImport (CORE_SERVICES_LIB)]
153 static extern bool SCNetworkReachabilityGetFlags (IntPtr reachability, out NetworkReachabilityFlags flags);
155 [DllImport (CORE_SERVICES_LIB)]
156 static extern bool SCNetworkReachabilitySetCallback (IntPtr reachability, SCNetworkReachabilityCallback callback, ref SCNetworkReachabilityContext context);
158 [DllImport (CORE_SERVICES_LIB)]
159 static extern bool SCNetworkReachabilityScheduleWithRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
161 [DllImport (CORE_SERVICES_LIB)]
162 static extern bool SCNetworkReachabilityUnscheduleFromRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
164 [StructLayout (LayoutKind.Explicit, Size = 28)]
166 [FieldOffset (0)] public byte sin_len;
167 [FieldOffset (1)] public byte sin_family;
169 public static sockaddr_in Create ()
171 return new sockaddr_in {
173 sin_family = 2 // AF_INET
178 [StructLayout (LayoutKind.Sequential)]
179 struct SCNetworkReachabilityContext {
180 public IntPtr version;
182 public IntPtr retain;
183 public IntPtr release;
184 public IntPtr copyDescription;
188 enum NetworkReachabilityFlags {
190 TransientConnection = 1 << 0,
192 ConnectionRequired = 1 << 2,
193 ConnectionOnTraffic = 1 << 3,
194 InterventionRequired = 1 << 4,
195 ConnectionOnDemand = 1 << 5,
196 IsLocalAddress = 1 << 16,
199 ConnectionAutomatic = ConnectionOnTraffic
204 SCNetworkReachabilityCallback callback;
205 bool scheduledWithRunLoop;
206 NetworkReachabilityFlags flags;
208 event NetworkAddressChangedEventHandler networkAddressChanged;
209 event NetworkAvailabilityChangedEventHandler networkAvailabilityChanged;
211 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
213 value (null, EventArgs.Empty);
214 networkAddressChanged += value;
217 remove { networkAddressChanged -= value; }
220 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
222 value (null, new NetworkAvailabilityEventArgs (IsAvailable));
223 networkAvailabilityChanged += value;
226 remove { networkAvailabilityChanged -= value; }
231 return (flags & NetworkReachabilityFlags.Reachable) != 0 &&
232 (flags & NetworkReachabilityFlags.ConnectionRequired) == 0;
236 public bool HasRegisteredEvents {
237 get { return networkAddressChanged != null || networkAvailabilityChanged != null; }
240 public MacNetworkChange ()
242 var sockaddr = sockaddr_in.Create ();
243 handle = SCNetworkReachabilityCreateWithAddress (IntPtr.Zero, ref sockaddr);
244 if (handle == IntPtr.Zero)
245 throw new Exception ("SCNetworkReachabilityCreateWithAddress returned NULL");
247 callback = new SCNetworkReachabilityCallback (HandleCallback);
248 var info = new SCNetworkReachabilityContext {
249 info = GCHandle.ToIntPtr (GCHandle.Alloc (this))
252 SCNetworkReachabilitySetCallback (handle, callback, ref info);
254 scheduledWithRunLoop =
255 LoadRunLoopMode () &&
256 SCNetworkReachabilityScheduleWithRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
258 SCNetworkReachabilityGetFlags (handle, out flags);
261 bool LoadRunLoopMode ()
263 var cfLibHandle = dlopen (CORE_FOUNDATION_LIB, 0);
264 if (cfLibHandle == IntPtr.Zero)
268 runLoopMode = dlsym (cfLibHandle, "kCFRunLoopDefaultMode");
269 if (runLoopMode != IntPtr.Zero) {
270 runLoopMode = Marshal.ReadIntPtr (runLoopMode);
271 return runLoopMode != IntPtr.Zero;
274 dlclose (cfLibHandle);
280 public void Dispose ()
283 if (handle == IntPtr.Zero)
286 if (scheduledWithRunLoop)
287 SCNetworkReachabilityUnscheduleFromRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
290 handle = IntPtr.Zero;
292 flags = NetworkReachabilityFlags.None;
293 scheduledWithRunLoop = false;
298 [MonoTouch.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
300 static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
302 if (info == IntPtr.Zero)
305 var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
306 if (instance == null || instance.flags == flags)
309 instance.flags = flags;
311 var addressChanged = instance.networkAddressChanged;
312 if (addressChanged != null)
313 addressChanged (null, EventArgs.Empty);
315 var availabilityChanged = instance.networkAvailabilityChanged;
316 if (availabilityChanged != null)
317 availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
321 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
323 internal sealed class LinuxNetworkChange : INetworkChange {
326 Availability = 1 << 0,
330 object _lock = new object ();
332 SocketAsyncEventArgs nl_args;
333 EventType pending_events;
336 NetworkAddressChangedEventHandler AddressChanged;
337 NetworkAvailabilityChangedEventHandler AvailabilityChanged;
339 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
340 add { Register (value); }
341 remove { Unregister (value); }
344 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
345 add { Register (value); }
346 remove { Unregister (value); }
349 public bool HasRegisteredEvents {
350 get { return AddressChanged != null || AvailabilityChanged != null; }
353 public void Dispose ()
357 //internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
364 IntPtr fd = CreateNLSocket ();
365 if (fd.ToInt64 () == -1)
368 var safeHandle = new SafeSocketHandle (fd, true);
370 nl_sock = new Socket (0, SocketType.Raw, ProtocolType.Udp, safeHandle);
371 nl_args = new SocketAsyncEventArgs ();
372 nl_args.SetBuffer (new byte [8192], 0, 8192);
373 nl_args.Completed += OnDataAvailable;
374 nl_sock.ReceiveAsync (nl_args);
379 // _lock is held by the caller
380 void MaybeCloseSocket ()
382 if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
385 CloseNLSocket (nl_sock.Handle);
386 GC.SuppressFinalize (nl_sock);
391 bool GetAvailability ()
393 NetworkInterface [] adapters = NetworkInterface.GetAllNetworkInterfaces ();
394 foreach (NetworkInterface n in adapters) {
395 // TODO: also check for a default route present?
396 if (n.NetworkInterfaceType == NetworkInterfaceType.Loopback)
398 if (n.OperationalStatus == OperationalStatus.Up)
404 void OnAvailabilityChanged (object unused)
406 NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
408 d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
411 void OnAddressChanged (object unused)
413 NetworkAddressChangedEventHandler d = AddressChanged;
415 d (null, EventArgs.Empty);
418 void OnEventDue (object unused)
422 evts = pending_events;
424 timer.Change (-1, -1);
426 if ((evts & EventType.Availability) != 0)
427 ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
428 if ((evts & EventType.Address) != 0)
429 ThreadPool.QueueUserWorkItem (OnAddressChanged);
432 void QueueEvent (EventType type)
436 timer = new Timer (OnEventDue);
437 if (pending_events == 0)
438 timer.Change (150, -1);
439 pending_events |= type;
443 unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
445 if (nl_sock == null) // Recent changes in Mono cause MaybeCloseSocket to be called before OnDataAvailable
448 fixed (byte *ptr = args.Buffer) {
449 type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
451 nl_sock.ReceiveAsync (nl_args);
456 void Register (NetworkAddressChangedEventHandler d)
462 void Register (NetworkAvailabilityChangedEventHandler d)
465 AvailabilityChanged += d;
468 void Unregister (NetworkAddressChangedEventHandler d)
476 void Unregister (NetworkAvailabilityChangedEventHandler d)
479 AvailabilityChanged -= d;
484 #if MONOTOUCH || MONODROID
485 const string LIBNAME = "__Internal";
487 const string LIBNAME = "MonoPosixHelper";
490 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
491 static extern IntPtr CreateNLSocket ();
493 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
494 static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
496 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
497 static extern IntPtr CloseNLSocket (IntPtr sock);