716afeffce1e7f3b55e8de441c9d0b9714aee7c3
[mono.git] / mcs / class / referencesource / System / net / System / Net / NetworkInformation / NetworkAddressChange.cs
1
2 namespace System.Net.NetworkInformation {
3
4     using System.Net;
5     using System.Net.NetworkInformation;
6     using System.Net.Sockets;
7     using System.Collections;
8     using System.Collections.Specialized;
9     using System.ComponentModel;
10     using System.Threading;
11     using System.Runtime.InteropServices;
12
13     [Flags]
14     internal enum StartIPOptions {Both = 3, None = 0, StartIPv4 = 1, StartIPv6 = 2}
15
16     public class NetworkAvailabilityEventArgs:EventArgs{
17         bool isAvailable;
18
19         internal NetworkAvailabilityEventArgs(bool isAvailable){
20             this.isAvailable = isAvailable;
21         }
22         
23         public bool IsAvailable{
24             get{
25                 return isAvailable;
26             }
27         }
28     }
29
30     [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors")]
31     public class NetworkChange{
32     #region designer support for System.Windows.dll
33         //introduced for supporting design-time loading of System.Windows.dll
34         [Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", true)]
35         [EditorBrowsable(EditorBrowsableState.Never)]
36         public NetworkChange() { }
37
38         //introduced for supporting design-time loading of System.Windows.dll
39         [Obsolete("This API supports the .NET Framework infrastructure and is not intended to be used directly from your code.", true)]
40         [EditorBrowsable(EditorBrowsableState.Never)]
41         public static void RegisterNetworkChange(NetworkChange nc) { }
42 #endregion
43         
44         static public event NetworkAvailabilityChangedEventHandler NetworkAvailabilityChanged{
45             add{
46                 AvailabilityChangeListener.Start(value);
47             }
48             remove{
49                 AvailabilityChangeListener.Stop(value);
50             }
51
52         }
53         
54         
55         static public event NetworkAddressChangedEventHandler NetworkAddressChanged{
56             add{
57                 AddressChangeListener.Start(value);
58             }
59             remove{
60                 AddressChangeListener.Stop(value);
61             }
62
63         }
64
65
66
67         static internal bool CanListenForNetworkChanges {
68             get{
69                 return true;
70             }
71         }
72
73         internal static class AvailabilityChangeListener{
74
75             static object syncObject = new object();
76             static private ListDictionary s_availabilityCallerArray = new ListDictionary();
77             static NetworkAddressChangedEventHandler addressChange = ChangedAddress;
78             static volatile bool isAvailable = false;
79             private static ContextCallback s_RunHandlerCallback = new ContextCallback(RunHandlerCallback);
80
81
82             private static void RunHandlerCallback(object state)
83             {
84                 ((NetworkAvailabilityChangedEventHandler) state)(null, new NetworkAvailabilityEventArgs(isAvailable));
85             }
86
87
88             private static void ChangedAddress(object sender, EventArgs eventArgs) {
89
90                 lock(syncObject){
91                     bool isAvailableNow = SystemNetworkInterface.InternalGetIsNetworkAvailable();
92
93                     if (isAvailableNow != isAvailable) {
94                         isAvailable = isAvailableNow;
95
96                         DictionaryEntry[] callerArray = new DictionaryEntry[s_availabilityCallerArray.Count];
97                         s_availabilityCallerArray.CopyTo(callerArray, 0);
98
99                         for (int i = 0; i < callerArray.Length; i++)
100                         {
101                             NetworkAvailabilityChangedEventHandler handler = (NetworkAvailabilityChangedEventHandler) callerArray[i].Key;
102                             ExecutionContext context = (ExecutionContext) callerArray[i].Value;
103                             if (context == null)
104                             {
105                                 handler(null, new NetworkAvailabilityEventArgs(isAvailable));
106                             }
107                             else
108                             {
109                                 ExecutionContext.Run(context.CreateCopy(), s_RunHandlerCallback, handler);
110                             }
111                         }
112                     }
113                 }
114             }
115
116
117
118             internal static void Start(NetworkAvailabilityChangedEventHandler caller){
119                 lock(syncObject){
120
121                     if (s_availabilityCallerArray.Count == 0) {
122                         isAvailable = NetworkInterface.GetIsNetworkAvailable();
123                         AddressChangeListener.UnsafeStart(addressChange);
124                     }
125
126                     if ((caller != null) && (!s_availabilityCallerArray.Contains(caller))) {
127                         s_availabilityCallerArray.Add(caller, ExecutionContext.Capture());
128                     }
129                 }
130             }
131
132
133             internal static void Stop(NetworkAvailabilityChangedEventHandler caller)
134             {
135                lock(syncObject){
136                     s_availabilityCallerArray.Remove(caller);
137                     if(s_availabilityCallerArray.Count == 0){
138                         AddressChangeListener.Stop(addressChange);
139                     }
140                 }
141             }
142         }
143
144
145         //helper class for detecting address change events.
146         internal unsafe static class AddressChangeListener{
147
148             static private ListDictionary s_callerArray = new ListDictionary();
149             static private ContextCallback s_runHandlerCallback = new ContextCallback(RunHandlerCallback);
150             static private RegisteredWaitHandle s_registeredWait;
151
152             //need to keep the reference so it isn't GC'd before the native call executes
153             static private bool s_isListening = false;
154             static private bool s_isPending = false;
155             static private SafeCloseSocketAndEvent s_ipv4Socket = null;
156             static private SafeCloseSocketAndEvent s_ipv6Socket = null;
157             static private WaitHandle s_ipv4WaitHandle = null;
158             static private WaitHandle s_ipv6WaitHandle = null;
159
160             //callback fired when an address change occurs
161             private static void AddressChangedCallback(object stateObject, bool signaled) {
162                 lock (s_callerArray) {
163
164                     //the listener was cancelled, which would only happen if we aren't listening
165                     //for more events.
166                     s_isPending = false;
167
168                     if (!s_isListening) {
169                         return;
170                     }
171
172                     s_isListening = false;
173
174                     // Need to copy the array so the callback can call start and stop
175                     DictionaryEntry[] callerArray = new DictionaryEntry[s_callerArray.Count];
176                     s_callerArray.CopyTo(callerArray, 0);
177
178                     try
179                     {
180                         //wait for the next address change
181                         StartHelper(null, false, (StartIPOptions)stateObject);
182                     }
183                     catch (NetworkInformationException nie)
184                     {
185                         if (Logging.On) Logging.Exception(Logging.Web, "AddressChangeListener", "AddressChangedCallback", nie);
186                     }
187
188                     for (int i = 0; i < callerArray.Length; i++)
189                     {
190                         NetworkAddressChangedEventHandler handler = (NetworkAddressChangedEventHandler) callerArray[i].Key;
191                         ExecutionContext context = (ExecutionContext) callerArray[i].Value;
192                         if (context == null)
193                         {
194                             handler(null, EventArgs.Empty);
195                         }
196                         else
197                         {
198                             ExecutionContext.Run(context.CreateCopy(), s_runHandlerCallback, handler);
199                         }
200                     }
201                 }
202             }
203
204             private static void RunHandlerCallback(object state)
205             {
206                 ((NetworkAddressChangedEventHandler) state)(null, EventArgs.Empty);
207             }
208
209
210             //start listening
211             internal static void Start(NetworkAddressChangedEventHandler caller)
212             {
213                 StartHelper(caller, true, StartIPOptions.Both);
214             }
215
216             internal static void UnsafeStart(NetworkAddressChangedEventHandler caller)
217             {
218                 StartHelper(caller, false, StartIPOptions.Both);
219             }
220
221             private static void StartHelper(NetworkAddressChangedEventHandler caller, bool captureContext, StartIPOptions startIPOptions)
222             {
223                 lock (s_callerArray) {
224                     // setup changedEvent and native overlapped struct.
225                     if(s_ipv4Socket == null){
226                         Socket.InitializeSockets();
227
228                         int blocking;
229
230                         if(Socket.OSSupportsIPv4){
231                             blocking = -1;
232                             s_ipv4Socket = SafeCloseSocketAndEvent.CreateWSASocketWithEvent(AddressFamily.InterNetwork, SocketType.Dgram, (ProtocolType)0, true, false);
233                             UnsafeNclNativeMethods.OSSOCK.ioctlsocket(s_ipv4Socket, IoctlSocketConstants.FIONBIO,ref blocking);
234                             s_ipv4WaitHandle = s_ipv4Socket.GetEventHandle();
235                         }
236
237                         if(Socket.OSSupportsIPv6){
238                             blocking = -1;
239                             s_ipv6Socket = SafeCloseSocketAndEvent.CreateWSASocketWithEvent(AddressFamily.InterNetworkV6, SocketType.Dgram, (ProtocolType)0, true, false);
240                             UnsafeNclNativeMethods.OSSOCK.ioctlsocket(s_ipv6Socket,IoctlSocketConstants.FIONBIO,ref blocking);
241                             s_ipv6WaitHandle = s_ipv6Socket.GetEventHandle();
242                         }
243                     }
244
245                     if ((caller != null) && (!s_callerArray.Contains(caller))) {
246                         s_callerArray.Add(caller, captureContext ? ExecutionContext.Capture() : null);
247                     }
248
249                     //if s_listener is not null, it means we are already actively listening
250                     if (s_isListening || s_callerArray.Count == 0) {
251                         return;
252                     }
253
254                     if(!s_isPending){
255
256                         int length;
257                         SocketError errorCode;
258
259                         if(Socket.OSSupportsIPv4 && (startIPOptions & StartIPOptions.StartIPv4) !=0){
260                             s_registeredWait = ThreadPool.UnsafeRegisterWaitForSingleObject(
261                                 s_ipv4WaitHandle,
262                                 new WaitOrTimerCallback(AddressChangedCallback),
263                                 StartIPOptions.StartIPv4,
264                                 -1,
265                                 true );
266
267                             errorCode = (SocketError) UnsafeNclNativeMethods.OSSOCK.WSAIoctl_Blocking(
268                                 s_ipv4Socket.DangerousGetHandle(),
269                                 (int) IOControlCode.AddressListChange,
270                                 null, 0, null, 0,
271                                 out length,
272                                 SafeNativeOverlapped.Zero, IntPtr.Zero);
273
274                             if (errorCode != SocketError.Success) {
275                                 NetworkInformationException exception = new NetworkInformationException();
276                                 if (exception.ErrorCode != (uint)SocketError.WouldBlock) {
277                                     throw exception;
278                                 }
279                             }
280
281                             errorCode = (SocketError)UnsafeNclNativeMethods.OSSOCK.WSAEventSelect(s_ipv4Socket, s_ipv4Socket.GetEventHandle().SafeWaitHandle, AsyncEventBits.FdAddressListChange);
282                             if (errorCode != SocketError.Success) {
283                                throw new NetworkInformationException();
284                             }
285                         }
286
287                         if(Socket.OSSupportsIPv6 && (startIPOptions & StartIPOptions.StartIPv6) !=0){
288                             s_registeredWait = ThreadPool.UnsafeRegisterWaitForSingleObject(
289                                 s_ipv6WaitHandle,
290                                 new WaitOrTimerCallback(AddressChangedCallback),
291                                 StartIPOptions.StartIPv6,
292                                 -1,
293                                 true );
294
295                             errorCode = (SocketError) UnsafeNclNativeMethods.OSSOCK.WSAIoctl_Blocking(
296                                 s_ipv6Socket.DangerousGetHandle(),
297                                 (int) IOControlCode.AddressListChange,
298                                 null, 0, null, 0,
299                                 out length,
300                                 SafeNativeOverlapped.Zero, IntPtr.Zero);
301
302                             if (errorCode != SocketError.Success) {
303                                 NetworkInformationException exception = new NetworkInformationException();
304                                 if (exception.ErrorCode != (uint)SocketError.WouldBlock) {
305                                     throw exception;
306                                 }
307                             }
308
309                             errorCode = (SocketError)UnsafeNclNativeMethods.OSSOCK.WSAEventSelect(s_ipv6Socket, s_ipv6Socket.GetEventHandle().SafeWaitHandle, AsyncEventBits.FdAddressListChange);
310                             if (errorCode != SocketError.Success) {
311                                throw new NetworkInformationException();
312                             }
313                         }
314                     }
315
316                     s_isListening = true;
317                     s_isPending = true;
318                 }
319             }
320
321
322
323             //stop listening
324             internal static void Stop(object caller)
325             {
326                 lock(s_callerArray){
327                     s_callerArray.Remove(caller);
328                     if (s_callerArray.Count == 0 && s_isListening) {
329                         s_isListening = false;
330                     }
331                 }
332             } //ends ignoreaddresschanges
333         }
334     }
335
336     public delegate void NetworkAddressChangedEventHandler(object sender, EventArgs e);
337     public delegate void NetworkAvailabilityChangedEventHandler(object sender, NetworkAvailabilityEventArgs e);
338 }
339