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 nl_sock = new Socket (0, SocketType.Raw, ProtocolType.Udp, fd);
369 nl_args = new SocketAsyncEventArgs ();
370 nl_args.SetBuffer (new byte [8192], 0, 8192);
371 nl_args.Completed += OnDataAvailable;
372 nl_sock.ReceiveAsync (nl_args);
377 // _lock is held by the caller
378 void MaybeCloseSocket ()
380 if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
383 CloseNLSocket (nl_sock.Handle);
384 GC.SuppressFinalize (nl_sock);
389 bool GetAvailability ()
391 NetworkInterface [] adapters = NetworkInterface.GetAllNetworkInterfaces ();
392 foreach (NetworkInterface n in adapters) {
393 // TODO: also check for a default route present?
394 if (n.NetworkInterfaceType == NetworkInterfaceType.Loopback)
396 if (n.OperationalStatus == OperationalStatus.Up)
402 void OnAvailabilityChanged (object unused)
404 NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
405 d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
408 void OnAddressChanged (object unused)
410 NetworkAddressChangedEventHandler d = AddressChanged;
411 d (null, EventArgs.Empty);
414 void OnEventDue (object unused)
418 evts = pending_events;
420 timer.Change (-1, -1);
422 if ((evts & EventType.Availability) != 0)
423 ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
424 if ((evts & EventType.Address) != 0)
425 ThreadPool.QueueUserWorkItem (OnAddressChanged);
428 void QueueEvent (EventType type)
432 timer = new Timer (OnEventDue);
433 if (pending_events == 0)
434 timer.Change (150, -1);
435 pending_events |= type;
439 unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
442 fixed (byte *ptr = args.Buffer) {
443 type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
445 nl_sock.ReceiveAsync (nl_args);
450 void Register (NetworkAddressChangedEventHandler d)
456 void Register (NetworkAvailabilityChangedEventHandler d)
459 AvailabilityChanged += d;
462 void Unregister (NetworkAddressChangedEventHandler d)
470 void Unregister (NetworkAvailabilityChangedEventHandler d)
473 AvailabilityChanged -= d;
478 #if MONOTOUCH || MONODROID
479 const string LIBNAME = "__Internal";
481 const string LIBNAME = "MonoPosixHelper";
484 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
485 static extern IntPtr CreateNLSocket ();
487 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
488 static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
490 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
491 static extern IntPtr CloseNLSocket (IntPtr sock);