357217659964fea2aabcd59beb116fe8b5866d08
[mono.git] / mcs / class / System / System.Net.NetworkInformation / NetworkChange.cs
1 //
2 // System.Net.NetworkInformation.NetworkChange
3 //
4 // Authors:
5 //   Gonzalo Paniagua Javier (LinuxNetworkChange) (gonzalo@novell.com)
6 //   Aaron Bockover (MacNetworkChange) (abock@xamarin.com)
7 //
8 // Copyright (c) 2006,2011 Novell, Inc. (http://www.novell.com)
9 // Copyright (c) 2013 Xamarin, Inc. (http://www.xamarin.com)
10 //
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:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
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.
29 //
30
31 using System;
32 using System.Net.Sockets;
33 using System.Runtime.InteropServices;
34 using System.Threading;
35
36 #if NETWORK_CHANGE_STANDALONE
37 namespace NetworkInformation {
38
39         public class NetworkAvailabilityEventArgs : EventArgs
40         {
41                 public bool IsAvailable { get; set; }
42
43                 public NetworkAvailabilityEventArgs (bool available)
44                 {
45                         IsAvailable = available;
46                 }
47         }
48
49         public delegate void NetworkAddressChangedEventHandler (object sender, EventArgs args);
50         public delegate void NetworkAvailabilityChangedEventHandler (object sender, NetworkAvailabilityEventArgs args);
51 #else
52 namespace System.Net.NetworkInformation {
53 #endif
54
55         internal interface INetworkChange : IDisposable {
56                 event NetworkAddressChangedEventHandler NetworkAddressChanged;
57                 event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
58                 bool HasRegisteredEvents { get; }
59         }
60
61         public sealed class NetworkChange {
62                 static INetworkChange networkChange;
63
64                 public static event NetworkAddressChangedEventHandler NetworkAddressChanged {
65                         add {
66                                 lock (typeof (INetworkChange)) {
67                                         MaybeCreate ();
68                                         if (networkChange != null)
69                                                 networkChange.NetworkAddressChanged += value;
70                                 }
71                         }
72
73                         remove {
74                                 lock (typeof (INetworkChange)) {
75                                         if (networkChange != null) {
76                                                 networkChange.NetworkAddressChanged -= value;
77                                                 MaybeDispose ();
78                                         }
79                                 }
80                         }
81                 }
82
83                 public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
84                         add {
85                                 lock (typeof (INetworkChange)) {
86                                         MaybeCreate ();
87                                         if (networkChange != null)
88                                                 networkChange.NetworkAvailabilityChanged += value;
89                                 }
90                         }
91
92                         remove {
93                                 lock (typeof (INetworkChange)) {
94                                         if (networkChange != null) {
95                                                 networkChange.NetworkAvailabilityChanged -= value;
96                                                 MaybeDispose ();
97                                         }
98                                 }
99                         }
100                 }
101
102                 static void MaybeCreate ()
103                 {
104                         if (networkChange != null)
105                                 return;
106
107                         try {
108                                 networkChange = new MacNetworkChange ();
109                         } catch {
110 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
111                                 networkChange = new LinuxNetworkChange ();
112 #endif
113                         }
114                 }
115
116                 static void MaybeDispose ()
117                 {
118                         if (networkChange != null && networkChange.HasRegisteredEvents) {
119                                 networkChange.Dispose ();
120                                 networkChange = null;
121                         }
122                 }
123         }
124
125         internal sealed class MacNetworkChange : INetworkChange
126         {
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";
130
131                 [UnmanagedFunctionPointerAttribute (CallingConvention.Cdecl)]
132                 delegate void SCNetworkReachabilityCallback (IntPtr target, NetworkReachabilityFlags flags, IntPtr info);
133
134                 [DllImport (DL_LIB)]
135                 static extern IntPtr dlopen (string path, int mode);
136
137                 [DllImport (DL_LIB)]
138                 static extern IntPtr dlsym (IntPtr handle, string symbol);
139
140                 [DllImport (DL_LIB)]
141                 static extern int dlclose (IntPtr handle);
142
143                 [DllImport (CORE_FOUNDATION_LIB)]
144                 static extern void CFRelease (IntPtr handle);
145
146                 [DllImport (CORE_FOUNDATION_LIB)]
147                 static extern IntPtr CFRunLoopGetMain ();
148
149                 [DllImport (CORE_SERVICES_LIB)]
150                 static extern IntPtr SCNetworkReachabilityCreateWithAddress (IntPtr allocator, ref sockaddr_in sockaddr);
151
152                 [DllImport (CORE_SERVICES_LIB)]
153                 static extern bool SCNetworkReachabilityGetFlags (IntPtr reachability, out NetworkReachabilityFlags flags);
154
155                 [DllImport (CORE_SERVICES_LIB)]
156                 static extern bool SCNetworkReachabilitySetCallback (IntPtr reachability, SCNetworkReachabilityCallback callback, ref SCNetworkReachabilityContext context);
157
158                 [DllImport (CORE_SERVICES_LIB)]
159                 static extern bool SCNetworkReachabilityScheduleWithRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
160
161                 [DllImport (CORE_SERVICES_LIB)]
162                 static extern bool SCNetworkReachabilityUnscheduleFromRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
163
164                 [StructLayout (LayoutKind.Explicit, Size = 28)]
165                 struct sockaddr_in {
166                         [FieldOffset (0)] public byte sin_len;
167                         [FieldOffset (1)] public byte sin_family;
168
169                         public static sockaddr_in Create ()
170                         {
171                                 return new sockaddr_in {
172                                         sin_len = 28,
173                                         sin_family = 2 // AF_INET
174                                 };
175                         }
176                 }
177
178                 [StructLayout (LayoutKind.Sequential)]
179                 struct SCNetworkReachabilityContext {
180                         public IntPtr version;
181                         public IntPtr info;
182                         public IntPtr retain;
183                         public IntPtr release;
184                         public IntPtr copyDescription;
185                 }
186
187                 [Flags]
188                 enum NetworkReachabilityFlags {
189                         None = 0,
190                         TransientConnection = 1 << 0,
191                         Reachable = 1 << 1,
192                         ConnectionRequired = 1 << 2,
193                         ConnectionOnTraffic = 1 << 3,
194                         InterventionRequired = 1 << 4,
195                         ConnectionOnDemand = 1 << 5,
196                         IsLocalAddress = 1 << 16,
197                         IsDirect = 1 << 17,
198                         IsWWAN = 1 << 18,
199                         ConnectionAutomatic = ConnectionOnTraffic
200                 }
201
202                 IntPtr handle;
203                 IntPtr runLoopMode;
204                 SCNetworkReachabilityCallback callback;
205                 bool scheduledWithRunLoop;
206                 NetworkReachabilityFlags flags;
207
208                 event NetworkAddressChangedEventHandler networkAddressChanged;
209                 event NetworkAvailabilityChangedEventHandler networkAvailabilityChanged;
210
211                 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
212                         add {
213                                 value (null, EventArgs.Empty);
214                                 networkAddressChanged += value;
215                         }
216
217                         remove { networkAddressChanged -= value; }
218                 }
219
220                 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
221                         add {
222                                 value (null, new NetworkAvailabilityEventArgs (IsAvailable));
223                                 networkAvailabilityChanged += value;
224                         }
225
226                         remove { networkAvailabilityChanged -= value; }
227                 }
228
229                 bool IsAvailable {
230                         get {
231                                 return (flags & NetworkReachabilityFlags.Reachable) != 0 &&
232                                         (flags & NetworkReachabilityFlags.ConnectionRequired) == 0;
233                         }
234                 }
235
236                 public bool HasRegisteredEvents {
237                         get { return networkAddressChanged != null || networkAvailabilityChanged != null; }
238                 }
239
240                 public MacNetworkChange ()
241                 {
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");
246
247                         callback = new SCNetworkReachabilityCallback (HandleCallback);
248                         var info = new SCNetworkReachabilityContext {
249                                 info = GCHandle.ToIntPtr (GCHandle.Alloc (this))
250                         };
251
252                         SCNetworkReachabilitySetCallback (handle, callback, ref info);
253
254                         scheduledWithRunLoop =
255                         LoadRunLoopMode () &&
256                                 SCNetworkReachabilityScheduleWithRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
257
258                         SCNetworkReachabilityGetFlags (handle, out flags);
259                 }
260
261                 bool LoadRunLoopMode ()
262                 {
263                         var cfLibHandle = dlopen (CORE_FOUNDATION_LIB, 0);
264                         if (cfLibHandle == IntPtr.Zero)
265                                 return false;
266
267                         try {
268                                 runLoopMode = dlsym (cfLibHandle, "kCFRunLoopDefaultMode");
269                                 if (runLoopMode != IntPtr.Zero) {
270                                         runLoopMode = Marshal.ReadIntPtr (runLoopMode);
271                                         return runLoopMode != IntPtr.Zero;
272                                 }
273                         } finally {
274                                 dlclose (cfLibHandle);
275                         }
276
277                         return false;
278                 }
279
280                 public void Dispose ()
281                 {
282                         lock (this) {
283                                 if (handle == IntPtr.Zero)
284                                         return;
285
286                                 if (scheduledWithRunLoop)
287                                         SCNetworkReachabilityUnscheduleFromRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
288
289                                 CFRelease (handle);
290                                 handle = IntPtr.Zero;
291                                 callback = null;
292                                 flags = NetworkReachabilityFlags.None;
293                                 scheduledWithRunLoop = false;
294                         }
295                 }
296
297 #if MONOTOUCH
298                 [MonoTouch.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
299 #endif
300                 static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
301                 {
302                         if (info == IntPtr.Zero)
303                                 return;
304
305                         var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
306                         if (instance == null || instance.flags == flags)
307                                 return;
308
309                         instance.flags = flags;
310
311                         var addressChanged = instance.networkAddressChanged;
312                         if (addressChanged != null)
313                                 addressChanged (null, EventArgs.Empty);
314
315                         var availabilityChanged = instance.networkAvailabilityChanged;
316                         if (availabilityChanged != null)
317                                 availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
318                 }
319         }
320
321 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
322
323         internal sealed class LinuxNetworkChange : INetworkChange {
324                 [Flags]
325                 enum EventType {
326                         Availability = 1 << 0,
327                         Address = 1 << 1,
328                 }
329
330                 object _lock = new object ();
331                 Socket nl_sock;
332                 SocketAsyncEventArgs nl_args;
333                 EventType pending_events;
334                 Timer timer;
335
336                 NetworkAddressChangedEventHandler AddressChanged;
337                 NetworkAvailabilityChangedEventHandler AvailabilityChanged;
338
339                 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
340                         add { Register (value); }
341                         remove { Unregister (value); }
342                 }
343
344                 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
345                         add { Register (value); }
346                         remove { Unregister (value); }
347                 }
348
349                 public bool HasRegisteredEvents {
350                         get { return AddressChanged != null || AvailabilityChanged != null; }
351                 }
352
353                 public void Dispose ()
354                 {
355                 }
356
357                 //internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
358
359                 bool EnsureSocket ()
360                 {
361                         lock (_lock) {
362                                 if (nl_sock != null)
363                                         return true;
364                                 IntPtr fd = CreateNLSocket ();
365                                 if (fd.ToInt64 () == -1)
366                                         return false;
367
368                                 var safeHandle = new SafeSocketHandle (fd, true);
369
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);
375                         }
376                         return true;
377                 }
378
379                 // _lock is held by the caller
380                 void MaybeCloseSocket ()
381                 {
382                         if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
383                                 return;
384
385                         CloseNLSocket (nl_sock.Handle);
386                         GC.SuppressFinalize (nl_sock);
387                         nl_sock = null;
388                         nl_args = null;
389                 }
390
391                 bool GetAvailability ()
392                 {
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)
397                                         continue;
398                                 if (n.OperationalStatus == OperationalStatus.Up)
399                                         return true;
400                         }
401                         return false;
402                 }
403
404                 void OnAvailabilityChanged (object unused)
405                 {
406                         NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
407                         if (d != null)
408                                 d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
409                 }
410
411                 void OnAddressChanged (object unused)
412                 {
413                         NetworkAddressChangedEventHandler d = AddressChanged;
414                         if (d != null)
415                                 d (null, EventArgs.Empty);
416                 }
417
418                 void OnEventDue (object unused)
419                 {
420                         EventType evts;
421                         lock (_lock) {
422                                 evts = pending_events;
423                                 pending_events = 0;
424                                 timer.Change (-1, -1);
425                         }
426                         if ((evts & EventType.Availability) != 0)
427                                 ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
428                         if ((evts & EventType.Address) != 0)
429                                 ThreadPool.QueueUserWorkItem (OnAddressChanged);
430                 }
431
432                 void QueueEvent (EventType type)
433                 {
434                         lock (_lock) {
435                                 if (timer == null)
436                                         timer = new Timer (OnEventDue);
437                                 if (pending_events == 0)
438                                         timer.Change (150, -1);
439                                 pending_events |= type;
440                         }
441                 }
442
443                 unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
444                 {
445                         if (nl_sock == null) // Recent changes in Mono cause MaybeCloseSocket to be called before OnDataAvailable
446                                 return;
447                         EventType type;
448                         fixed (byte *ptr = args.Buffer) {
449                                 type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
450                         }
451                         nl_sock.ReceiveAsync (nl_args);
452                         if (type != 0)
453                                 QueueEvent (type);
454                 }
455
456                 void Register (NetworkAddressChangedEventHandler d)
457                 {
458                         EnsureSocket ();
459                         AddressChanged += d;
460                 }
461
462                 void Register (NetworkAvailabilityChangedEventHandler d)
463                 {
464                         EnsureSocket ();
465                         AvailabilityChanged += d;
466                 }
467
468                 void Unregister (NetworkAddressChangedEventHandler d)
469                 {
470                         lock (_lock) {
471                                 AddressChanged -= d;
472                                 MaybeCloseSocket ();
473                         }
474                 }
475
476                 void Unregister (NetworkAvailabilityChangedEventHandler d)
477                 {
478                         lock (_lock) {
479                                 AvailabilityChanged -= d;
480                                 MaybeCloseSocket ();
481                         }
482                 }
483
484 #if MONOTOUCH || MONODROID
485                 const string LIBNAME = "__Internal";
486 #else
487                 const string LIBNAME = "MonoPosixHelper";
488 #endif
489
490                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
491                 static extern IntPtr CreateNLSocket ();
492
493                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
494                 static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
495
496                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
497                 static extern IntPtr CloseNLSocket (IntPtr sock);
498         }
499
500 #endif
501
502 }