Merge pull request #572 from jack-pappas/sockets-ipproto
[mono.git] / mcs / class / System / System.Net.NetworkInformation / NetworkChange.cs
1 //
2 // System.Net.NetworkInformation.NetworkChange
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
6 //  Aaron Bockover (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.Net.Sockets;
32 using System.Runtime.InteropServices;
33 using System.Threading;
34
35 namespace System.Net.NetworkInformation {
36         internal interface INetworkChange {
37                 event NetworkAddressChangedEventHandler NetworkAddressChanged;
38                 event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged;
39         }
40
41         public sealed class NetworkChange {
42                 static INetworkChange networkChange;
43
44                 static NetworkChange ()
45                 {
46                         if (MacNetworkChange.IsEnabled) {
47                                 networkChange = new MacNetworkChange ();
48                         } else {
49                                 networkChange = new LinuxNetworkChange ();
50                         }
51                 }
52
53                 public static event NetworkAddressChangedEventHandler NetworkAddressChanged {
54                         add { networkChange.NetworkAddressChanged += value; }
55                         remove { networkChange.NetworkAddressChanged -= value; }
56                 }
57
58                 public static event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
59                         add { networkChange.NetworkAvailabilityChanged += value; }
60                         remove { networkChange.NetworkAvailabilityChanged -= value; }
61                 }
62         }
63
64         internal sealed class MacNetworkChange : INetworkChange {
65                 public static bool IsEnabled {
66                         get { return mono_sc_reachability_enabled () != 0; }
67                 }
68
69                 event NetworkAddressChangedEventHandler networkAddressChanged;
70                 event NetworkAvailabilityChangedEventHandler networkAvailabilityChanged;
71
72                 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
73                         add {
74                                 if (value != null) {
75                                         MaybeInitialize ();
76                                         networkAddressChanged += value;
77                                         value (null, EventArgs.Empty);
78                                 }
79                         }
80
81                         remove {
82                                 networkAddressChanged -= value;
83                                 MaybeDispose ();
84                         }
85                 }
86
87                 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
88                         add {
89                                 if (value != null) {
90                                         MaybeInitialize ();
91                                         networkAvailabilityChanged += value;
92                                         var available = handle != IntPtr.Zero && mono_sc_reachability_is_available (handle) != 0;
93                                         value (null, new NetworkAvailabilityEventArgs (available));
94                                 }
95                         }
96
97                         remove {
98                                 networkAvailabilityChanged -= value;
99                                 MaybeDispose ();
100                         }
101                 }
102
103                 IntPtr handle;
104                 MonoSCReachabilityCallback callback;
105
106                 void Callback (int available)
107                 {
108                         var addressChanged = networkAddressChanged;
109                         if (addressChanged != null) {
110                                 addressChanged (null, EventArgs.Empty);
111                         }
112
113                         var availabilityChanged = networkAvailabilityChanged;
114                         if (availabilityChanged != null) {
115                                 availabilityChanged (null, new NetworkAvailabilityEventArgs (available != 0));
116                         }
117                 }
118
119                 void MaybeInitialize ()
120                 {
121                         lock (this) {
122                                 if (handle == IntPtr.Zero) {
123                                         callback = new MonoSCReachabilityCallback (Callback);
124                                         handle = mono_sc_reachability_new (callback);
125                                 }
126                         }
127                 }
128
129                 void MaybeDispose ()
130                 {
131                         lock (this) {
132                                 var addressChanged = networkAddressChanged;
133                                 var availabilityChanged = networkAvailabilityChanged;
134                                 if (handle != IntPtr.Zero && addressChanged == null && availabilityChanged == null) {
135                                         mono_sc_reachability_free (handle);
136                                         handle = IntPtr.Zero;
137                                 }
138                         }
139                 }
140
141 #if MONOTOUCH || MONODROID
142                 const string LIBNAME = "__Internal";
143 #else
144                 const string LIBNAME = "MonoPosixHelper";
145 #endif
146
147                 delegate void MonoSCReachabilityCallback (int available);
148
149                 [DllImport (LIBNAME)]
150                 static extern int mono_sc_reachability_enabled ();
151
152                 [DllImport (LIBNAME)]
153                 static extern IntPtr mono_sc_reachability_new (MonoSCReachabilityCallback callback);
154
155                 [DllImport (LIBNAME)]
156                 static extern void mono_sc_reachability_free (IntPtr handle);
157
158                 [DllImport (LIBNAME)]
159                 static extern int mono_sc_reachability_is_available (IntPtr handle);
160         }
161
162         internal sealed class LinuxNetworkChange : INetworkChange {
163                 [Flags]
164                 enum EventType {
165                         Availability = 1 << 0,
166                         Address = 1 << 1,
167                 }
168
169                 object _lock = new object ();
170                 Socket nl_sock;
171                 SocketAsyncEventArgs nl_args;
172                 EventType pending_events;
173                 Timer timer;
174
175                 NetworkAddressChangedEventHandler AddressChanged;
176                 NetworkAvailabilityChangedEventHandler AvailabilityChanged;
177
178                 public event NetworkAddressChangedEventHandler NetworkAddressChanged {
179                         add { Register (value); }
180                         remove { Unregister (value); }
181                 }
182
183                 public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged {
184                         add { Register (value); }
185                         remove { Unregister (value); }
186                 }
187
188                 //internal Socket (AddressFamily family, SocketType type, ProtocolType proto, IntPtr sock)
189
190                 bool EnsureSocket ()
191                 {
192                         lock (_lock) {
193                                 if (nl_sock != null)
194                                         return true;
195                                 IntPtr fd = CreateNLSocket ();
196                                 if (fd.ToInt64 () == -1)
197                                         return false;
198
199                                 nl_sock = new Socket (0, SocketType.Raw, ProtocolType.Udp, fd);
200                                 nl_args = new SocketAsyncEventArgs ();
201                                 nl_args.SetBuffer (new byte [8192], 0, 8192);
202                                 nl_args.Completed += OnDataAvailable;
203                                 nl_sock.ReceiveAsync (nl_args);
204                         }
205                         return true;
206                 }
207
208                 // _lock is held by the caller
209                 void MaybeCloseSocket ()
210                 {
211                         if (nl_sock == null || AvailabilityChanged != null || AddressChanged != null)
212                                 return;
213
214                         CloseNLSocket (nl_sock.Handle);
215                         GC.SuppressFinalize (nl_sock);
216                         nl_sock = null;
217                         nl_args = null;
218                 }
219
220                 bool GetAvailability ()
221                 {
222                         NetworkInterface [] adapters = NetworkInterface.GetAllNetworkInterfaces ();
223                         foreach (NetworkInterface n in adapters) {
224                                 // TODO: also check for a default route present?
225                                 if (n.NetworkInterfaceType == NetworkInterfaceType.Loopback)
226                                         continue;
227                                 if (n.OperationalStatus == OperationalStatus.Up)
228                                         return true;
229                         }
230                         return false;
231                 }
232
233                 void OnAvailabilityChanged (object unused)
234                 {
235                         NetworkAvailabilityChangedEventHandler d = AvailabilityChanged;
236                         d (null, new NetworkAvailabilityEventArgs (GetAvailability ()));
237                 }
238
239                 void OnAddressChanged (object unused)
240                 {
241                         NetworkAddressChangedEventHandler d = AddressChanged;
242                         d (null, EventArgs.Empty);
243                 }
244
245                 void OnEventDue (object unused)
246                 {
247                         EventType evts;
248                         lock (_lock) {
249                                 evts = pending_events;
250                                 pending_events = 0;
251                                 timer.Change (-1, -1);
252                         }
253                         if ((evts & EventType.Availability) != 0)
254                                 ThreadPool.QueueUserWorkItem (OnAvailabilityChanged);
255                         if ((evts & EventType.Address) != 0)
256                                 ThreadPool.QueueUserWorkItem (OnAddressChanged);
257                 }
258
259                 void QueueEvent (EventType type)
260                 {
261                         lock (_lock) {
262                                 if (timer == null)
263                                         timer = new Timer (OnEventDue);
264                                 if (pending_events == 0)
265                                         timer.Change (150, -1);
266                                 pending_events |= type;
267                         }
268                 }
269
270                 unsafe void OnDataAvailable (object sender, SocketAsyncEventArgs args)
271                 {
272                         EventType type;
273                         fixed (byte *ptr = args.Buffer) {       
274                                 type = ReadEvents (nl_sock.Handle, new IntPtr (ptr), args.BytesTransferred, 8192);
275                         }
276                         nl_sock.ReceiveAsync (nl_args);
277                         if (type != 0)
278                                 QueueEvent (type);
279                 }
280
281                 void Register (NetworkAddressChangedEventHandler d)
282                 {
283                         EnsureSocket ();
284                         AddressChanged += d;
285                 }
286
287                 void Register (NetworkAvailabilityChangedEventHandler d)
288                 {
289                         EnsureSocket ();
290                         AvailabilityChanged += d;
291                 }
292
293                 void Unregister (NetworkAddressChangedEventHandler d)
294                 {
295                         lock (_lock) {
296                                 AddressChanged -= d;
297                                 MaybeCloseSocket ();
298                         }
299                 }
300
301                 void Unregister (NetworkAvailabilityChangedEventHandler d)
302                 {
303                         lock (_lock) {
304                                 AvailabilityChanged -= d;
305                                 MaybeCloseSocket ();
306                         }
307                 }
308
309 #if MONOTOUCH || MONODROID
310                 const string LIBNAME = "__Internal";
311 #else
312                 const string LIBNAME = "MonoPosixHelper";
313 #endif
314
315                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
316                 static extern IntPtr CreateNLSocket ();
317
318                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
319                 static extern EventType ReadEvents (IntPtr sock, IntPtr buffer, int count, int size);
320
321                 [DllImport (LIBNAME, CallingConvention=CallingConvention.Cdecl)]
322                 static extern IntPtr CloseNLSocket (IntPtr sock);
323         }
324 }
325