2 // System.Net.NetworkInformation.Ping
5 // Gonzalo Paniagua Javier (gonzalo@novell.com)
6 // Atsushi Enomoto (atsushi@ximian.com)
8 // Copyright (c) 2006-2007 Novell, Inc. (http://www.novell.com)
9 // Copyright 2015 Xamarin Inc.
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
34 using System
.Diagnostics
;
35 using System
.Globalization
;
36 using System
.ComponentModel
;
37 using System
.Net
.Sockets
;
38 using System
.Security
.Principal
;
39 using System
.Security
.Cryptography
;
40 using System
.Runtime
.InteropServices
;
41 using System
.Threading
;
42 using System
.Threading
.Tasks
;
44 namespace System
.Net
.NetworkInformation
{
45 [MonoTODO ("IPv6 support is missing")]
46 public class Ping
: Component
, IDisposable
49 [StructLayout(LayoutKind
.Sequential
)]
50 struct cap_user_header_t
52 public UInt32 version
;
56 [StructLayout(LayoutKind
.Sequential
)]
57 struct cap_user_data_t
59 public UInt32 effective
;
60 public UInt32 permitted
;
61 public UInt32 inheritable
;
64 const int DefaultCount
= 1;
65 static readonly string [] PingBinPaths
= new string [] {
73 static readonly string PingBinPath
;
74 static bool canSendPrivileged
;
76 const int default_timeout
= 4000; // 4 sec.
79 // Request 32-bit capabilities by using version 1
80 const UInt32 _LINUX_CAPABILITY_VERSION_1
= 0x19980330;
82 static readonly byte [] default_buffer
= new byte [0];
85 BackgroundWorker worker
;
86 object user_async_state
;
87 CancellationTokenSource cts
;
89 public event PingCompletedEventHandler PingCompleted
;
91 #if !MONOTOUCH && !ORBIS
94 if (Environment
.OSVersion
.Platform
== PlatformID
.Unix
) {
95 CheckLinuxCapabilities ();
96 if (!canSendPrivileged
&& WindowsIdentity
.GetCurrent ().Name
== "root")
97 canSendPrivileged
= true;
99 // Since different Unix systems can have different path to bin, we try some
100 // of the known ones.
101 foreach (string ping_path
in PingBinPaths
)
102 if (File
.Exists (ping_path
)) {
103 PingBinPath
= ping_path
;
108 canSendPrivileged
= true;
110 if (PingBinPath
== null)
111 PingBinPath
= "/bin/ping"; // default, fallback value
117 // Generate a new random 16 bit identifier for every ping
118 RNGCryptoServiceProvider rng
= new RNGCryptoServiceProvider ();
119 byte [] randomIdentifier
= new byte [2];
120 rng
.GetBytes (randomIdentifier
);
121 identifier
= (ushort)(randomIdentifier
[0] + (randomIdentifier
[1] << 8));
124 #if !MONOTOUCH && !ORBIS
125 [DllImport ("libc", EntryPoint
="capget")]
126 static extern int capget (ref cap_user_header_t header
, ref cap_user_data_t data
);
128 static void CheckLinuxCapabilities ()
131 cap_user_header_t header
= new cap_user_header_t ();
132 cap_user_data_t data
= new cap_user_data_t ();
134 header
.version
= _LINUX_CAPABILITY_VERSION_1
;
139 ret
= capget (ref header
, ref data
);
140 } catch (Exception
) {
146 canSendPrivileged
= (data
.effective
& (1 << 13)) != 0;
148 canSendPrivileged
= false;
153 void IDisposable
.Dispose ()
157 protected void OnPingCompleted (PingCompletedEventArgs e
)
159 user_async_state
= null;
167 if (PingCompleted
!= null)
168 PingCompleted (this, e
);
173 public PingReply
Send (IPAddress address
)
175 return Send (address
, default_timeout
);
178 public PingReply
Send (IPAddress address
, int timeout
)
180 return Send (address
, timeout
, default_buffer
);
183 public PingReply
Send (IPAddress address
, int timeout
, byte [] buffer
)
185 return Send (address
, timeout
, buffer
, new PingOptions ());
188 public PingReply
Send (string hostNameOrAddress
)
190 return Send (hostNameOrAddress
, default_timeout
);
193 public PingReply
Send (string hostNameOrAddress
, int timeout
)
195 return Send (hostNameOrAddress
, timeout
, default_buffer
);
198 public PingReply
Send (string hostNameOrAddress
, int timeout
, byte [] buffer
)
200 return Send (hostNameOrAddress
, timeout
, buffer
, new PingOptions ());
203 public PingReply
Send (string hostNameOrAddress
, int timeout
, byte [] buffer
, PingOptions options
)
205 IPAddress
[] addresses
= Dns
.GetHostAddresses (hostNameOrAddress
);
206 return Send (addresses
[0], timeout
, buffer
, options
);
209 public PingReply
Send (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
)
212 throw new ArgumentNullException ("address");
214 throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
216 throw new ArgumentNullException ("buffer");
217 if (buffer
.Length
> 65500)
218 throw new ArgumentException ("buffer");
219 // options can be null.
222 throw new InvalidOperationException ();
224 if (canSendPrivileged
)
225 return SendPrivileged (address
, timeout
, buffer
, options
);
226 return SendUnprivileged (address
, timeout
, buffer
, options
);
231 private PingReply
SendPrivileged (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
)
233 IPEndPoint target
= new IPEndPoint (address
, 0);
235 // FIXME: support IPv6
236 using (Socket s
= new Socket (AddressFamily
.InterNetwork
, SocketType
.Raw
, ProtocolType
.Icmp
)) {
237 if (options
!= null) {
238 s
.DontFragment
= options
.DontFragment
;
239 s
.Ttl
= (short) options
.Ttl
;
241 s
.SendTimeout
= timeout
;
242 s
.ReceiveTimeout
= timeout
;
243 // not sure why Identifier = 0 is unacceptable ...
244 IcmpMessage send
= new IcmpMessage (8, 0, identifier
, 0, buffer
);
245 byte [] bytes
= send
.GetBytes ();
246 s
.SendBufferSize
= bytes
.Length
;
247 s
.SendTo (bytes
, bytes
.Length
, SocketFlags
.None
, target
);
249 DateTime sentTime
= DateTime
.Now
;
252 bytes
= new byte [100];
254 EndPoint endpoint
= target
;
255 SocketError error
= 0;
256 int rc
= s
.ReceiveFrom (bytes
, 0, 100, SocketFlags
.None
,
257 ref endpoint
, out error
);
259 if (error
!= SocketError
.Success
) {
260 if (error
== SocketError
.TimedOut
) {
261 return new PingReply (null, new byte [0], options
, 0, IPStatus
.TimedOut
);
263 throw new NotSupportedException (String
.Format ("Unexpected socket error during ping request: {0}", error
));
265 long rtt
= (long) (DateTime
.Now
- sentTime
).TotalMilliseconds
;
266 int headerLength
= (bytes
[0] & 0xF) << 2;
267 int bodyLength
= rc
- headerLength
;
269 // Ping reply to different request. discard it.
270 if (!((IPEndPoint
) endpoint
).Address
.Equals (target
.Address
)) {
271 long t
= timeout
- rtt
;
273 return new PingReply (null, new byte [0], options
, 0, IPStatus
.TimedOut
);
274 s
.ReceiveTimeout
= (int) t
;
278 IcmpMessage recv
= new IcmpMessage (bytes
, headerLength
, bodyLength
);
280 /* discard ping reply to different request or echo requests if running on same host. */
281 if (recv
.Identifier
!= identifier
|| recv
.Type
== 8) {
282 long t
= timeout
- rtt
;
284 return new PingReply (null, new byte [0], options
, 0, IPStatus
.TimedOut
);
285 s
.ReceiveTimeout
= (int) t
;
289 return new PingReply (address
, recv
.Data
, options
, rtt
, recv
.IPStatus
);
294 private PingReply
SendUnprivileged (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
)
296 #if MONO_FEATURE_PROCESS_START
297 DateTime sentTime
= DateTime
.UtcNow
;
299 Process ping
= new Process ();
300 string args
= BuildPingArgs (address
, timeout
, options
);
303 ping
.StartInfo
.FileName
= PingBinPath
;
304 ping
.StartInfo
.Arguments
= args
;
306 ping
.StartInfo
.CreateNoWindow
= true;
307 ping
.StartInfo
.UseShellExecute
= false;
309 ping
.StartInfo
.RedirectStandardOutput
= true;
310 ping
.StartInfo
.RedirectStandardError
= true;
312 IPStatus status
= IPStatus
.Unknown
;
316 #pragma warning disable 219
317 string stdout
= ping
.StandardOutput
.ReadToEnd ();
318 string stderr
= ping
.StandardError
.ReadToEnd ();
319 #pragma warning restore 219
321 trip_time
= (long) (DateTime
.UtcNow
- sentTime
).TotalMilliseconds
;
322 if (!ping
.WaitForExit (timeout
) || (ping
.HasExited
&& ping
.ExitCode
== 2))
323 status
= IPStatus
.TimedOut
;
324 else if (ping
.ExitCode
== 0)
325 status
= IPStatus
.Success
;
326 else if (ping
.ExitCode
== 1)
327 status
= IPStatus
.TtlExpired
;
335 return new PingReply (address
, buffer
, options
, trip_time
, status
);
337 throw new PlatformNotSupportedException ("Ping is not supported on this platform.");
338 #endif // MONO_FEATURE_PROCESS_START
344 public void SendAsync (IPAddress address
, int timeout
, byte [] buffer
, object userToken
)
346 SendAsync (address
, default_timeout
, default_buffer
, new PingOptions (), userToken
);
349 public void SendAsync (IPAddress address
, int timeout
, object userToken
)
351 SendAsync (address
, default_timeout
, default_buffer
, userToken
);
354 public void SendAsync (IPAddress address
, object userToken
)
356 SendAsync (address
, default_timeout
, userToken
);
359 public void SendAsync (string hostNameOrAddress
, int timeout
, byte [] buffer
, object userToken
)
361 SendAsync (hostNameOrAddress
, timeout
, buffer
, new PingOptions (), userToken
);
364 public void SendAsync (string hostNameOrAddress
, int timeout
, byte [] buffer
, PingOptions options
, object userToken
)
366 IPAddress address
= Dns
.GetHostEntry (hostNameOrAddress
).AddressList
[0];
367 SendAsync (address
, timeout
, buffer
, options
, userToken
);
370 public void SendAsync (string hostNameOrAddress
, int timeout
, object userToken
)
372 SendAsync (hostNameOrAddress
, timeout
, default_buffer
, userToken
);
375 public void SendAsync (string hostNameOrAddress
, object userToken
)
377 SendAsync (hostNameOrAddress
, default_timeout
, userToken
);
380 public void SendAsync (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
, object userToken
)
382 if ((worker
!= null) || (cts
!= null))
383 throw new InvalidOperationException ("Another SendAsync operation is in progress");
385 worker
= new BackgroundWorker ();
386 worker
.DoWork
+= delegate (object o
, DoWorkEventArgs ea
) {
388 user_async_state
= ea
.Argument
;
389 ea
.Result
= Send (address
, timeout
, buffer
, options
);
390 } catch (Exception ex
) {
394 worker
.WorkerSupportsCancellation
= true;
395 worker
.RunWorkerCompleted
+= delegate (object o
, RunWorkerCompletedEventArgs ea
) {
396 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
397 OnPingCompleted (new PingCompletedEventArgs (ea
.Error
, ea
.Cancelled
, user_async_state
, ea
.Result
as PingReply
));
399 worker
.RunWorkerAsync (userToken
);
404 public void SendAsyncCancel ()
412 throw new InvalidOperationException ("SendAsync operation is not in progress");
413 worker
.CancelAsync ();
424 public IcmpMessage (byte [] bytes
, int offset
, int size
)
426 this.bytes
= new byte [size
];
427 Buffer
.BlockCopy (bytes
, offset
, this.bytes
, 0, size
);
431 public IcmpMessage (byte type
, byte code
, ushort identifier
, ushort sequence
, byte [] data
)
433 bytes
= new byte [data
.Length
+ 8];
436 bytes
[4] = (byte) (identifier
& 0xFF);
437 bytes
[5] = (byte) ((int) identifier
>> 8);
438 bytes
[6] = (byte) (sequence
& 0xFF);
439 bytes
[7] = (byte) ((int) sequence
>> 8);
440 Buffer
.BlockCopy (data
, 0, bytes
, 8, data
.Length
);
442 ushort checksum
= ComputeChecksum (bytes
);
443 bytes
[2] = (byte) (checksum
& 0xFF);
444 bytes
[3] = (byte) ((int) checksum
>> 8);
448 get { return bytes [0]; }
452 get { return bytes [1]; }
455 public ushort Identifier
{
456 get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
459 public ushort Sequence
{
460 get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
463 public byte [] Data
{
465 byte [] data
= new byte [bytes
.Length
- 8];
466 Buffer
.BlockCopy (bytes
, 8, data
, 0, data
.Length
);
471 public byte [] GetBytes ()
476 static ushort ComputeChecksum (byte [] data
)
479 for (int i
= 0; i
< data
.Length
; i
+= 2) {
480 ushort us
= i
+ 1 < data
.Length
? data
[i
+ 1] : (byte) 0;
485 ret
= (ret
>> 16) + (ret
& 0xFFFF);
486 return (ushort) ~ ret
;
489 public IPStatus IPStatus
{
493 return IPStatus
.Success
;
494 case 3: // destination unreacheable
497 return IPStatus
.DestinationNetworkUnreachable
;
499 return IPStatus
.DestinationHostUnreachable
;
501 return IPStatus
.DestinationProtocolUnreachable
;
503 return IPStatus
.DestinationPortUnreachable
;
505 return IPStatus
.BadOption
; // FIXME: likely wrong
507 return IPStatus
.BadRoute
; // not sure if it is correct
513 return IPStatus
.TimeExceeded
;
515 return IPStatus
.TtlReassemblyTimeExceeded
;
519 return IPStatus
.ParameterProblem
;
521 return IPStatus
.SourceQuench
;
523 return IPStatus
.Success
;
525 return IPStatus
.Unknown
;
526 //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
531 private string BuildPingArgs (IPAddress address
, int timeout
, PingOptions options
)
533 CultureInfo culture
= CultureInfo
.InvariantCulture
;
534 StringBuilder args
= new StringBuilder ();
535 uint t
= Convert
.ToUInt32 (Math
.Floor ((timeout
+ 1000) / 1000.0));
536 bool is_mac
= Platform
.IsMacOS
;
538 args
.AppendFormat (culture
, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount
, t
, options
.Ttl
);
540 args
.AppendFormat (culture
, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount
, t
, options
.Ttl
);
542 args
.Append (options
.DontFragment
? "do " : "dont ");
543 else if (options
.DontFragment
)
546 args
.Append (address
.ToString ());
548 return args
.ToString ();
552 public Task
<PingReply
> SendPingAsync (IPAddress address
, int timeout
, byte [] buffer
)
554 return SendPingAsync (address
, default_timeout
, default_buffer
, new PingOptions ());
557 public Task
<PingReply
> SendPingAsync (IPAddress address
, int timeout
)
559 return SendPingAsync (address
, default_timeout
, default_buffer
);
562 public Task
<PingReply
> SendPingAsync (IPAddress address
)
564 return SendPingAsync (address
, default_timeout
);
567 public Task
<PingReply
> SendPingAsync (string hostNameOrAddress
, int timeout
, byte [] buffer
)
569 return SendPingAsync (hostNameOrAddress
, timeout
, buffer
, new PingOptions ());
572 public Task
<PingReply
> SendPingAsync (string hostNameOrAddress
, int timeout
, byte [] buffer
, PingOptions options
)
574 IPAddress address
= Dns
.GetHostEntry (hostNameOrAddress
).AddressList
[0];
575 return SendPingAsync (address
, timeout
, buffer
, options
);
578 public Task
<PingReply
> SendPingAsync (string hostNameOrAddress
, int timeout
)
580 return SendPingAsync (hostNameOrAddress
, timeout
, default_buffer
);
583 public Task
<PingReply
> SendPingAsync (string hostNameOrAddress
)
585 return SendPingAsync (hostNameOrAddress
, default_timeout
);
588 public Task
<PingReply
> SendPingAsync (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
)
590 if ((worker
!= null) || (cts
!= null))
591 throw new InvalidOperationException ("Another SendAsync operation is in progress");
593 cts
= new CancellationTokenSource();
595 var task
= Task
<PingReply
>.Factory
.StartNew (
596 () => Send (address
, timeout
, buffer
, options
), cts
.Token
);
598 task
.ContinueWith ((t
) => {
600 OnPingCompleted (new PingCompletedEventArgs (null, true, null, null));
601 else if (t
.IsFaulted
)
602 OnPingCompleted (new PingCompletedEventArgs (t
.Exception
, false, null, null));
604 OnPingCompleted (new PingCompletedEventArgs (null, false, null, t
.Result
));