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 ()
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;
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 #if MONOTOUCH || MOBILE_STATIC
303 [MonoTouch.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
305 static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
307 if (info == IntPtr.Zero)
310 var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
311 if (instance == null || instance.flags == flags)
314 instance.flags = flags;
316 var addressChanged = instance.networkAddressChanged;
317 if (addressChanged != null)
318 addressChanged (null, EventArgs.Empty);
320 var availabilityChanged = instance.networkAvailabilityChanged;
321 if (availabilityChanged != null)
322 availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
325 #endif // !MONOTOUCH_WATCH
327 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
329 internal sealed class LinuxNetworkChange : INetworkChange {
332 Availability = 1 << 0,
336 object _lock = new object ();
338 SocketAsyncEventArgs nl_args;
339 EventType pending_events;
342 NetworkAddressChangedEventHandler AddressChanged;
343 NetworkAvailabilityChangedEventHandler AvailabilityChanged;
345 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
346 add { Register (value); }
347 remove { Unregister (value); }
350 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
351 add { Register (value); }
352 remove { Unregister (value); }
355 public bool HasRegisteredEvents {
356 get { return AddressChanged != null || AvailabilityChanged != null; }
359 public void Dispose ()
363 //internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
370 IntPtr fd = CreateNLSocket ();
371 if (fd.ToInt64 () == -1)
374 var safeHandle = new SafeSocketHandle (fd, true);
376 nl_sock = new Socket (0, SocketType.Raw, ProtocolType.Udp, safeHandle);
377 nl_args = new SocketAsyncEventArgs ();
378 nl_args.SetBuffer (new byte [8192], 0, 8192);
379 nl_args.Completed += OnDataAvailable;
380 nl_sock.ReceiveAsync (nl_args);
385 // _lock is held by the caller
386 void MaybeCloseSocket ()
388 if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
391 CloseNLSocket (nl_sock.Handle);
392 GC.SuppressFinalize (nl_sock);
397 bool GetAvailability ()
399 NetworkInterface [] adapters = NetworkInterface.GetAllNetworkInterfaces ();
400 foreach (NetworkInterface n in adapters) {
401 // TODO: also check for a default route present?
402 if (n.NetworkInterfaceType == NetworkInterfaceType.Loopback)
404 if (n.OperationalStatus == OperationalStatus.Up)
410 void OnAvailabilityChanged (object unused)
412 NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
414 d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
417 void OnAddressChanged (object unused)
419 NetworkAddressChangedEventHandler d = AddressChanged;
421 d (null, EventArgs.Empty);
424 void OnEventDue (object unused)
428 evts = pending_events;
430 timer.Change (-1, -1);
432 if ((evts & EventType.Availability) != 0)
433 ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
434 if ((evts & EventType.Address) != 0)
435 ThreadPool.QueueUserWorkItem (OnAddressChanged);
438 void QueueEvent (EventType type)
442 timer = new Timer (OnEventDue);
443 if (pending_events == 0)
444 timer.Change (150, -1);
445 pending_events |= type;
449 unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
451 if (nl_sock == null) // Recent changes in Mono cause MaybeCloseSocket to be called before OnDataAvailable
454 fixed (byte *ptr = args.Buffer) {
455 type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
457 nl_sock.ReceiveAsync (nl_args);
462 void Register (NetworkAddressChangedEventHandler d)
468 void Register (NetworkAvailabilityChangedEventHandler d)
471 AvailabilityChanged += d;
474 void Unregister (NetworkAddressChangedEventHandler d)
482 void Unregister (NetworkAvailabilityChangedEventHandler d)
485 AvailabilityChanged -= d;
490 #if MONOTOUCH || MONODROID
491 const string LIBNAME = "__Internal";
493 const string LIBNAME = "MonoPosixHelper";
496 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
497 static extern IntPtr CreateNLSocket ();
499 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
500 static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
502 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
503 static extern IntPtr CloseNLSocket (IntPtr sock);