[System] Throw PlatformNotSupportedException if NetworkInformation.NetworkChange...
[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
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
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 #if MONOTOUCH
303                 [MonoTouch.MonoPInvokeCallback (typeof (SCNetworkReachabilityCallback))]
304 #endif
305                 static void HandleCallback (IntPtr reachability, NetworkReachabilityFlags flags, IntPtr info)
306                 {
307                         if (info == IntPtr.Zero)
308                                 return;
309
310                         var instance = GCHandle.FromIntPtr (info).Target as MacNetworkChange;
311                         if (instance == null || instance.flags == flags)
312                                 return;
313
314                         instance.flags = flags;
315
316                         var addressChanged = instance.networkAddressChanged;
317                         if (addressChanged != null)
318                                 addressChanged (null, EventArgs.Empty);
319
320                         var availabilityChanged = instance.networkAvailabilityChanged;
321                         if (availabilityChanged != null)
322                                 availabilityChanged (null, new NetworkAvailabilityEventArgs (instance.IsAvailable));
323                 }
324         }
325 #endif // !MONOTOUCH_WATCH
326
327 #if !NETWORK_CHANGE_STANDALONE && !MONOTOUCH
328
329         internal sealed class LinuxNetworkChange : INetworkChange {
330                 [Flags]
331                 enum EventType {
332                         Availability = 1 << 0,
333                         Address = 1 << 1,
334                 }
335
336                 object _lock = new object ();
337                 Socket nl_sock;
338                 SocketAsyncEventArgs nl_args;
339                 EventType pending_events;
340                 Timer timer;
341
342                 NetworkAddressChangedEventHandler AddressChanged;
343                 NetworkAvailabilityChangedEventHandler AvailabilityChanged;
344
345                 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
346                         add { Register (value); }
347                         remove { Unregister (value); }
348                 }
349
350                 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
351                         add { Register (value); }
352                         remove { Unregister (value); }
353                 }
354
355                 public bool HasRegisteredEvents {
356                         get { return AddressChanged != null || AvailabilityChanged != null; }
357                 }
358
359                 public void Dispose ()
360                 {
361                 }
362
363                 //internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
364
365                 bool EnsureSocket ()
366                 {
367                         lock (_lock) {
368                                 if (nl_sock != null)
369                                         return true;
370                                 IntPtr fd = CreateNLSocket ();
371                                 if (fd.ToInt64 () == -1)
372                                         return false;
373
374                                 var safeHandle = new SafeSocketHandle (fd, true);
375
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);
381                         }
382                         return true;
383                 }
384
385                 // _lock is held by the caller
386                 void MaybeCloseSocket ()
387                 {
388                         if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
389                                 return;
390
391                         CloseNLSocket (nl_sock.Handle);
392                         GC.SuppressFinalize (nl_sock);
393                         nl_sock = null;
394                         nl_args = null;
395                 }
396
397                 bool GetAvailability ()
398                 {
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)
403                                         continue;
404                                 if (n.OperationalStatus == OperationalStatus.Up)
405                                         return true;
406                         }
407                         return false;
408                 }
409
410                 void OnAvailabilityChanged (object unused)
411                 {
412                         NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
413                         if (d != null)
414                                 d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
415                 }
416
417                 void OnAddressChanged (object unused)
418                 {
419                         NetworkAddressChangedEventHandler d = AddressChanged;
420                         if (d != null)
421                                 d (null, EventArgs.Empty);
422                 }
423
424                 void OnEventDue (object unused)
425                 {
426                         EventType evts;
427                         lock (_lock) {
428                                 evts = pending_events;
429                                 pending_events = 0;
430                                 timer.Change (-1, -1);
431                         }
432                         if ((evts & EventType.Availability) != 0)
433                                 ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
434                         if ((evts & EventType.Address) != 0)
435                                 ThreadPool.QueueUserWorkItem (OnAddressChanged);
436                 }
437
438                 void QueueEvent (EventType type)
439                 {
440                         lock (_lock) {
441                                 if (timer == null)
442                                         timer = new Timer (OnEventDue);
443                                 if (pending_events == 0)
444                                         timer.Change (150, -1);
445                                 pending_events |= type;
446                         }
447                 }
448
449                 unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
450                 {
451                         if (nl_sock == null) // Recent changes in Mono cause MaybeCloseSocket to be called before OnDataAvailable
452                                 return;
453                         EventType type;
454                         fixed (byte *ptr = args.Buffer) {
455                                 type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
456                         }
457                         nl_sock.ReceiveAsync (nl_args);
458                         if (type != 0)
459                                 QueueEvent (type);
460                 }
461
462                 void Register (NetworkAddressChangedEventHandler d)
463                 {
464                         EnsureSocket ();
465                         AddressChanged += d;
466                 }
467
468                 void Register (NetworkAvailabilityChangedEventHandler d)
469                 {
470                         EnsureSocket ();
471                         AvailabilityChanged += d;
472                 }
473
474                 void Unregister (NetworkAddressChangedEventHandler d)
475                 {
476                         lock (_lock) {
477                                 AddressChanged -= d;
478                                 MaybeCloseSocket ();
479                         }
480                 }
481
482                 void Unregister (NetworkAvailabilityChangedEventHandler d)
483                 {
484                         lock (_lock) {
485                                 AvailabilityChanged -= d;
486                                 MaybeCloseSocket ();
487                         }
488                 }
489
490 #if MONOTOUCH || MONODROID
491                 const string LIBNAME = "__Internal";
492 #else
493                 const string LIBNAME = "MonoPosixHelper";
494 #endif
495
496                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
497                 static extern IntPtr CreateNLSocket ();
498
499                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
500                 static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
501
502                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
503                 static extern IntPtr CloseNLSocket (IntPtr sock);
504         }
505
506 #endif
507
508 }