Merge pull request #5198 from BrzVlad/fix-binprot-stats
[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 MONOTOUCH_WATCH || ORBIS
105                         throw new PlatformNotSupportedException ("NetworkInformation.NetworkChange is not supported on the current platform.");
106 #else
107                         if (networkChange != null)
108                                 return;
109
110                         try {
111                                 networkChange = new MacNetworkChange ();
112                         } catch {
113 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
114                                 networkChange = new LinuxNetworkChange ();
115 #endif
116                         }
117 #endif // MONOTOUCH_WATCH
118                 }
119
120                 static void MaybeDispose ()
121                 {
122                         if (networkChange != null && networkChange.HasRegisteredEvents) {
123                                 networkChange.Dispose ();
124                                 networkChange = null;
125                         }
126                 }
127         }
128
129 #if !MONOTOUCH_WATCH && !ORBIS
130         internal sealed class MacNetworkChange : INetworkChange
131         {
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";
135
136                 [UnmanagedFunctionPointerAttribute (CallingConvention.Cdecl)]
137                 delegate void SCNetworkReachabilityCallback (IntPtr target, NetworkReachabilityFlags flags, IntPtr info);
138
139                 [DllImport (DL_LIB)]
140                 static extern IntPtr dlopen (string path, int mode);
141
142                 [DllImport (DL_LIB)]
143                 static extern IntPtr dlsym (IntPtr handle, string symbol);
144
145                 [DllImport (DL_LIB)]
146                 static extern int dlclose (IntPtr handle);
147
148                 [DllImport (CORE_FOUNDATION_LIB)]
149                 static extern void CFRelease (IntPtr handle);
150
151                 [DllImport (CORE_FOUNDATION_LIB)]
152                 static extern IntPtr CFRunLoopGetMain ();
153
154                 [DllImport (CORE_SERVICES_LIB)]
155                 static extern IntPtr SCNetworkReachabilityCreateWithAddress (IntPtr allocator, ref sockaddr_in sockaddr);
156
157                 [DllImport (CORE_SERVICES_LIB)]
158                 static extern bool SCNetworkReachabilityGetFlags (IntPtr reachability, out NetworkReachabilityFlags flags);
159
160                 [DllImport (CORE_SERVICES_LIB)]
161                 static extern bool SCNetworkReachabilitySetCallback (IntPtr reachability, SCNetworkReachabilityCallback callback, ref SCNetworkReachabilityContext context);
162
163                 [DllImport (CORE_SERVICES_LIB)]
164                 static extern bool SCNetworkReachabilityScheduleWithRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
165
166                 [DllImport (CORE_SERVICES_LIB)]
167                 static extern bool SCNetworkReachabilityUnscheduleFromRunLoop (IntPtr reachability, IntPtr runLoop, IntPtr runLoopMode);
168
169                 [StructLayout (LayoutKind.Explicit, Size = 28)]
170                 struct sockaddr_in {
171                         [FieldOffset (0)] public byte sin_len;
172                         [FieldOffset (1)] public byte sin_family;
173
174                         public static sockaddr_in Create ()
175                         {
176                                 return new sockaddr_in {
177                                         sin_len = 28,
178                                         sin_family = 2 // AF_INET
179                                 };
180                         }
181                 }
182
183                 [StructLayout (LayoutKind.Sequential)]
184                 struct SCNetworkReachabilityContext {
185                         public IntPtr version;
186                         public IntPtr info;
187                         public IntPtr retain;
188                         public IntPtr release;
189                         public IntPtr copyDescription;
190                 }
191
192                 [Flags]
193                 enum NetworkReachabilityFlags {
194                         None = 0,
195                         TransientConnection = 1 << 0,
196                         Reachable = 1 << 1,
197                         ConnectionRequired = 1 << 2,
198                         ConnectionOnTraffic = 1 << 3,
199                         InterventionRequired = 1 << 4,
200                         ConnectionOnDemand = 1 << 5,
201                         IsLocalAddress = 1 << 16,
202                         IsDirect = 1 << 17,
203                         IsWWAN = 1 << 18,
204                         ConnectionAutomatic = ConnectionOnTraffic
205                 }
206
207                 IntPtr handle;
208                 IntPtr runLoopMode;
209                 SCNetworkReachabilityCallback callback;
210                 bool scheduledWithRunLoop;
211                 NetworkReachabilityFlags flags;
212
213                 event NetworkAddressChangedEventHandler networkAddressChanged;
214                 event NetworkAvailabilityChangedEventHandler networkAvailabilityChanged;
215
216                 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
217                         add {
218                                 value (null, EventArgs.Empty);
219                                 networkAddressChanged += value;
220                         }
221
222                         remove { networkAddressChanged -= value; }
223                 }
224
225                 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
226                         add {
227                                 value (null, new NetworkAvailabilityEventArgs (IsAvailable));
228                                 networkAvailabilityChanged += value;
229                         }
230
231                         remove { networkAvailabilityChanged -= value; }
232                 }
233
234                 bool IsAvailable {
235                         get {
236                                 return (flags & NetworkReachabilityFlags.Reachable) != 0 &&
237                                         (flags & NetworkReachabilityFlags.ConnectionRequired) == 0;
238                         }
239                 }
240
241                 public bool HasRegisteredEvents {
242                         get { return networkAddressChanged != null || networkAvailabilityChanged != null; }
243                 }
244
245                 public MacNetworkChange ()
246                 {
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");
251
252                         callback = new SCNetworkReachabilityCallback (HandleCallback);
253                         var info = new SCNetworkReachabilityContext {
254                                 info = GCHandle.ToIntPtr (GCHandle.Alloc (this))
255                         };
256
257                         SCNetworkReachabilitySetCallback (handle, callback, ref info);
258
259                         scheduledWithRunLoop =
260                         LoadRunLoopMode () &&
261                                 SCNetworkReachabilityScheduleWithRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
262
263                         SCNetworkReachabilityGetFlags (handle, out flags);
264                 }
265
266                 bool LoadRunLoopMode ()
267                 {
268                         var cfLibHandle = dlopen (CORE_FOUNDATION_LIB, 0);
269                         if (cfLibHandle == IntPtr.Zero)
270                                 return false;
271
272                         try {
273                                 runLoopMode = dlsym (cfLibHandle, "kCFRunLoopDefaultMode");
274                                 if (runLoopMode != IntPtr.Zero) {
275                                         runLoopMode = Marshal.ReadIntPtr (runLoopMode);
276                                         return runLoopMode != IntPtr.Zero;
277                                 }
278                         } finally {
279                                 dlclose (cfLibHandle);
280                         }
281
282                         return false;
283                 }
284
285                 public void Dispose ()
286                 {
287                         lock (this) {
288                                 if (handle == IntPtr.Zero)
289                                         return;
290
291                                 if (scheduledWithRunLoop)
292                                         SCNetworkReachabilityUnscheduleFromRunLoop (handle, CFRunLoopGetMain (), runLoopMode);
293
294                                 CFRelease (handle);
295                                 handle = IntPtr.Zero;
296                                 callback = null;
297                                 flags = NetworkReachabilityFlags.None;
298                                 scheduledWithRunLoop = false;
299                         }
300                 }
301
302                 [Mono.Util.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
303                 static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
304                 {
305                         if (info == IntPtr.Zero)
306                                 return;
307
308                         var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
309                         if (instance == null || instance.flags == flags)
310                                 return;
311
312                         instance.flags = flags;
313
314                         var addressChanged = instance.networkAddressChanged;
315                         if (addressChanged != null)
316                                 addressChanged (null, EventArgs.Empty);
317
318                         var availabilityChanged = instance.networkAvailabilityChanged;
319                         if (availabilityChanged != null)
320                                 availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
321                 }
322         }
323 #endif // !MONOTOUCH_WATCH
324
325 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH && !ORBIS
326
327         internal sealed class LinuxNetworkChange : INetworkChange {
328                 [Flags]
329                 enum EventType {
330                         Availability = 1 << 0,
331                         Address = 1 << 1,
332                 }
333
334                 object _lock = new object ();
335                 Socket nl_sock;
336                 SocketAsyncEventArgs nl_args;
337                 EventType pending_events;
338                 Timer timer;
339
340                 NetworkAddressChangedEventHandler AddressChanged;
341                 NetworkAvailabilityChangedEventHandler AvailabilityChanged;
342
343                 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
344                         add { Register (value); }
345                         remove { Unregister (value); }
346                 }
347
348                 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
349                         add { Register (value); }
350                         remove { Unregister (value); }
351                 }
352
353                 public bool HasRegisteredEvents {
354                         get { return AddressChanged != null || AvailabilityChanged != null; }
355                 }
356
357                 public void Dispose ()
358                 {
359                 }
360
361                 //internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
362
363                 bool EnsureSocket ()
364                 {
365                         lock (_lock) {
366                                 if (nl_sock != null)
367                                         return true;
368                                 IntPtr fd = CreateNLSocket ();
369                                 if (fd.ToInt64 () == -1)
370                                         return false;
371
372                                 var safeHandle = new SafeSocketHandle (fd, true);
373
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);
379                         }
380                         return true;
381                 }
382
383                 // _lock is held by the caller
384                 void MaybeCloseSocket ()
385                 {
386                         if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
387                                 return;
388
389                         CloseNLSocket (nl_sock.Handle);
390                         GC.SuppressFinalize (nl_sock);
391                         nl_sock = null;
392                         nl_args = null;
393                 }
394
395                 bool GetAvailability ()
396                 {
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)
401                                         continue;
402                                 if (n.OperationalStatus == OperationalStatus.Up)
403                                         return true;
404                         }
405                         return false;
406                 }
407
408                 void OnAvailabilityChanged (object unused)
409                 {
410                         NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
411                         if (d != null)
412                                 d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
413                 }
414
415                 void OnAddressChanged (object unused)
416                 {
417                         NetworkAddressChangedEventHandler d = AddressChanged;
418                         if (d != null)
419                                 d (null, EventArgs.Empty);
420                 }
421
422                 void OnEventDue (object unused)
423                 {
424                         EventType evts;
425                         lock (_lock) {
426                                 evts = pending_events;
427                                 pending_events = 0;
428                                 timer.Change (-1, -1);
429                         }
430                         if ((evts & EventType.Availability) != 0)
431                                 ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
432                         if ((evts & EventType.Address) != 0)
433                                 ThreadPool.QueueUserWorkItem (OnAddressChanged);
434                 }
435
436                 void QueueEvent (EventType type)
437                 {
438                         lock (_lock) {
439                                 if (timer == null)
440                                         timer = new Timer (OnEventDue);
441                                 if (pending_events == 0)
442                                         timer.Change (150, -1);
443                                 pending_events |= type;
444                         }
445                 }
446
447                 unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
448                 {
449                         if (nl_sock == null) // Recent changes in Mono cause MaybeCloseSocket to be called before OnDataAvailable
450                                 return;
451                         EventType type;
452                         fixed (byte *ptr = args.Buffer) {
453                                 type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
454                         }
455                         nl_sock.ReceiveAsync (nl_args);
456                         if (type != 0)
457                                 QueueEvent (type);
458                 }
459
460                 void Register (NetworkAddressChangedEventHandler d)
461                 {
462                         EnsureSocket ();
463                         AddressChanged += d;
464                 }
465
466                 void Register (NetworkAvailabilityChangedEventHandler d)
467                 {
468                         EnsureSocket ();
469                         AvailabilityChanged += d;
470                 }
471
472                 void Unregister (NetworkAddressChangedEventHandler d)
473                 {
474                         lock (_lock) {
475                                 AddressChanged -= d;
476                                 MaybeCloseSocket ();
477                         }
478                 }
479
480                 void Unregister (NetworkAvailabilityChangedEventHandler d)
481                 {
482                         lock (_lock) {
483                                 AvailabilityChanged -= d;
484                                 MaybeCloseSocket ();
485                         }
486                 }
487
488 #if MONOTOUCH || MONODROID
489                 const string LIBNAME = "__Internal";
490 #else
491                 const string LIBNAME = "MonoPosixHelper";
492 #endif
493
494                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
495                 static extern IntPtr CreateNLSocket ();
496
497                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
498                 static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
499
500                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
501                 static extern IntPtr CloseNLSocket (IntPtr sock);
502         }
503
504 #endif
505
506 }