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)
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System
.Diagnostics
;
34 using System
.Globalization
;
35 using System
.ComponentModel
;
36 using System
.Net
.Sockets
;
37 using System
.Security
.Principal
;
38 using System
.Security
.Cryptography
;
39 using System
.Runtime
.InteropServices
;
40 using System
.Threading
;
41 using System
.Threading
.Tasks
;
43 namespace System
.Net
.NetworkInformation
{
44 [MonoTODO ("IPv6 support is missing")]
45 public class Ping
: Component
, IDisposable
47 [StructLayout(LayoutKind
.Sequential
)]
48 struct cap_user_header_t
50 public UInt32 version
;
54 [StructLayout(LayoutKind
.Sequential
)]
55 struct cap_user_data_t
57 public UInt32 effective
;
58 public UInt32 permitted
;
59 public UInt32 inheritable
;
62 const int DefaultCount
= 1;
63 static readonly string [] PingBinPaths
= new string [] {
71 static readonly string PingBinPath
;
72 const int default_timeout
= 4000; // 4 sec.
75 // This value is correct as of Linux kernel version 2.6.25.9
76 // See /usr/include/linux/capability.h
77 const UInt32 linux_cap_version
= 0x20071026;
79 static readonly byte [] default_buffer
= new byte [0];
80 static bool canSendPrivileged
;
83 BackgroundWorker worker
;
84 object user_async_state
;
85 CancellationTokenSource cts
;
87 public event PingCompletedEventHandler PingCompleted
;
91 if (Environment
.OSVersion
.Platform
== PlatformID
.Unix
) {
92 CheckLinuxCapabilities ();
93 if (!canSendPrivileged
&& WindowsIdentity
.GetCurrent ().Name
== "root")
94 canSendPrivileged
= true;
96 // Since different Unix systems can have different path to bin, we try some
98 foreach (string ping_path
in PingBinPaths
)
99 if (File
.Exists (ping_path
)) {
100 PingBinPath
= ping_path
;
105 canSendPrivileged
= true;
107 if (PingBinPath
== null)
108 PingBinPath
= "/bin/ping"; // default, fallback value
113 // Generate a new random 16 bit identifier for every ping
114 RNGCryptoServiceProvider rng
= new RNGCryptoServiceProvider ();
115 byte [] randomIdentifier
= new byte [2];
116 rng
.GetBytes (randomIdentifier
);
117 identifier
= (ushort)(randomIdentifier
[0] + (randomIdentifier
[1] << 8));
120 [DllImport ("libc", EntryPoint
="capget")]
121 static extern int capget (ref cap_user_header_t header
, ref cap_user_data_t data
);
123 static void CheckLinuxCapabilities ()
126 cap_user_header_t header
= new cap_user_header_t ();
127 cap_user_data_t data
= new cap_user_data_t ();
129 header
.version
= linux_cap_version
;
134 ret
= capget (ref header
, ref data
);
135 } catch (Exception
) {
141 canSendPrivileged
= (data
.effective
& (1 << 13)) != 0;
143 canSendPrivileged
= false;
147 void IDisposable
.Dispose ()
151 protected void OnPingCompleted (PingCompletedEventArgs e
)
153 user_async_state
= null;
157 if (PingCompleted
!= null)
158 PingCompleted (this, e
);
163 public PingReply
Send (IPAddress address
)
165 return Send (address
, default_timeout
);
168 public PingReply
Send (IPAddress address
, int timeout
)
170 return Send (address
, timeout
, default_buffer
);
173 public PingReply
Send (IPAddress address
, int timeout
, byte [] buffer
)
175 return Send (address
, timeout
, buffer
, new PingOptions ());
178 public PingReply
Send (string hostNameOrAddress
)
180 return Send (hostNameOrAddress
, default_timeout
);
183 public PingReply
Send (string hostNameOrAddress
, int timeout
)
185 return Send (hostNameOrAddress
, timeout
, default_buffer
);
188 public PingReply
Send (string hostNameOrAddress
, int timeout
, byte [] buffer
)
190 return Send (hostNameOrAddress
, timeout
, buffer
, new PingOptions ());
193 public PingReply
Send (string hostNameOrAddress
, int timeout
, byte [] buffer
, PingOptions options
)
195 IPAddress
[] addresses
= Dns
.GetHostAddresses (hostNameOrAddress
);
196 return Send (addresses
[0], timeout
, buffer
, options
);
199 static IPAddress
GetNonLoopbackIP ()
201 foreach (IPAddress addr
in Dns
.GetHostByName (Dns
.GetHostName ()).AddressList
)
202 if (!IPAddress
.IsLoopback (addr
))
204 throw new InvalidOperationException ("Could not resolve non-loopback IP address for localhost");
207 public PingReply
Send (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
)
210 throw new ArgumentNullException ("address");
212 throw new ArgumentOutOfRangeException ("timeout", "timeout must be non-negative integer");
214 throw new ArgumentNullException ("buffer");
215 if (buffer
.Length
> 65500)
216 throw new ArgumentException ("buffer");
217 // options can be null.
219 if (canSendPrivileged
)
220 return SendPrivileged (address
, timeout
, buffer
, options
);
221 return SendUnprivileged (address
, timeout
, buffer
, options
);
224 private PingReply
SendPrivileged (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
)
226 IPEndPoint target
= new IPEndPoint (address
, 0);
227 IPEndPoint client
= new IPEndPoint (GetNonLoopbackIP (), 0);
229 // FIXME: support IPv6
230 using (Socket s
= new Socket (AddressFamily
.InterNetwork
, SocketType
.Raw
, ProtocolType
.Icmp
)) {
231 if (options
!= null) {
232 s
.DontFragment
= options
.DontFragment
;
233 s
.Ttl
= (short) options
.Ttl
;
235 s
.SendTimeout
= timeout
;
236 s
.ReceiveTimeout
= timeout
;
237 // not sure why Identifier = 0 is unacceptable ...
238 IcmpMessage send
= new IcmpMessage (8, 0, identifier
, 0, buffer
);
239 byte [] bytes
= send
.GetBytes ();
240 s
.SendBufferSize
= bytes
.Length
;
241 s
.SendTo (bytes
, bytes
.Length
, SocketFlags
.None
, target
);
243 DateTime sentTime
= DateTime
.Now
;
246 bytes
= new byte [100];
248 EndPoint endpoint
= client
;
250 int rc
= s
.ReceiveFrom_nochecks_exc (bytes
, 0, 100, SocketFlags
.None
,
251 ref endpoint
, false, out error
);
254 if (error
== (int) SocketError
.TimedOut
) {
255 return new PingReply (null, new byte [0], options
, 0, IPStatus
.TimedOut
);
257 throw new NotSupportedException (String
.Format ("Unexpected socket error during ping request: {0}", error
));
259 long rtt
= (long) (DateTime
.Now
- sentTime
).TotalMilliseconds
;
260 int headerLength
= (bytes
[0] & 0xF) << 2;
261 int bodyLength
= rc
- headerLength
;
263 // Ping reply to different request. discard it.
264 if (!((IPEndPoint
) endpoint
).Address
.Equals (target
.Address
)) {
265 long t
= timeout
- rtt
;
267 return new PingReply (null, new byte [0], options
, 0, IPStatus
.TimedOut
);
268 s
.ReceiveTimeout
= (int) t
;
272 IcmpMessage recv
= new IcmpMessage (bytes
, headerLength
, bodyLength
);
274 /* discard ping reply to different request or echo requests if running on same host. */
275 if (recv
.Identifier
!= identifier
|| recv
.Type
== 8) {
276 long t
= timeout
- rtt
;
278 return new PingReply (null, new byte [0], options
, 0, IPStatus
.TimedOut
);
279 s
.ReceiveTimeout
= (int) t
;
283 return new PingReply (address
, recv
.Data
, options
, rtt
, recv
.IPStatus
);
288 private PingReply
SendUnprivileged (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
)
290 #if MONO_FEATURE_PROCESS_START
291 DateTime sentTime
= DateTime
.UtcNow
;
293 Process ping
= new Process ();
294 string args
= BuildPingArgs (address
, timeout
, options
);
297 ping
.StartInfo
.FileName
= PingBinPath
;
298 ping
.StartInfo
.Arguments
= args
;
300 ping
.StartInfo
.CreateNoWindow
= true;
301 ping
.StartInfo
.UseShellExecute
= false;
303 ping
.StartInfo
.RedirectStandardOutput
= true;
304 ping
.StartInfo
.RedirectStandardError
= true;
306 IPStatus status
= IPStatus
.Unknown
;
310 #pragma warning disable 219
311 string stdout
= ping
.StandardOutput
.ReadToEnd ();
312 string stderr
= ping
.StandardError
.ReadToEnd ();
313 #pragma warning restore 219
315 trip_time
= (long) (DateTime
.UtcNow
- sentTime
).TotalMilliseconds
;
316 if (!ping
.WaitForExit (timeout
) || (ping
.HasExited
&& ping
.ExitCode
== 2))
317 status
= IPStatus
.TimedOut
;
318 else if (ping
.ExitCode
== 0)
319 status
= IPStatus
.Success
;
320 else if (ping
.ExitCode
== 1)
321 status
= IPStatus
.TtlExpired
;
329 return new PingReply (address
, buffer
, options
, trip_time
, status
);
331 throw new NotSupportedException ("Ping is not supported on this platform.");
332 #endif // MONO_FEATURE_PROCESS_START
337 public void SendAsync (IPAddress address
, int timeout
, byte [] buffer
, object userToken
)
339 SendAsync (address
, default_timeout
, default_buffer
, new PingOptions (), userToken
);
342 public void SendAsync (IPAddress address
, int timeout
, object userToken
)
344 SendAsync (address
, default_timeout
, default_buffer
, userToken
);
347 public void SendAsync (IPAddress address
, object userToken
)
349 SendAsync (address
, default_timeout
, userToken
);
352 public void SendAsync (string hostNameOrAddress
, int timeout
, byte [] buffer
, object userToken
)
354 SendAsync (hostNameOrAddress
, timeout
, buffer
, new PingOptions (), userToken
);
357 public void SendAsync (string hostNameOrAddress
, int timeout
, byte [] buffer
, PingOptions options
, object userToken
)
359 IPAddress address
= Dns
.GetHostEntry (hostNameOrAddress
).AddressList
[0];
360 SendAsync (address
, timeout
, buffer
, options
, userToken
);
363 public void SendAsync (string hostNameOrAddress
, int timeout
, object userToken
)
365 SendAsync (hostNameOrAddress
, timeout
, default_buffer
, userToken
);
368 public void SendAsync (string hostNameOrAddress
, object userToken
)
370 SendAsync (hostNameOrAddress
, default_timeout
, userToken
);
373 public void SendAsync (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
, object userToken
)
375 if ((worker
!= null) || (cts
!= null))
376 throw new InvalidOperationException ("Another SendAsync operation is in progress");
378 worker
= new BackgroundWorker ();
379 worker
.DoWork
+= delegate (object o
, DoWorkEventArgs ea
) {
381 user_async_state
= ea
.Argument
;
382 ea
.Result
= Send (address
, timeout
, buffer
, options
);
383 } catch (Exception ex
) {
387 worker
.WorkerSupportsCancellation
= true;
388 worker
.RunWorkerCompleted
+= delegate (object o
, RunWorkerCompletedEventArgs ea
) {
389 // Note that RunWorkerCompletedEventArgs.UserState cannot be used (LAMESPEC)
390 OnPingCompleted (new PingCompletedEventArgs (ea
.Error
, ea
.Cancelled
, user_async_state
, ea
.Result
as PingReply
));
392 worker
.RunWorkerAsync (userToken
);
397 public void SendAsyncCancel ()
405 throw new InvalidOperationException ("SendAsync operation is not in progress");
406 worker
.CancelAsync ();
416 public IcmpMessage (byte [] bytes
, int offset
, int size
)
418 this.bytes
= new byte [size
];
419 Buffer
.BlockCopy (bytes
, offset
, this.bytes
, 0, size
);
423 public IcmpMessage (byte type
, byte code
, ushort identifier
, ushort sequence
, byte [] data
)
425 bytes
= new byte [data
.Length
+ 8];
428 bytes
[4] = (byte) (identifier
& 0xFF);
429 bytes
[5] = (byte) ((int) identifier
>> 8);
430 bytes
[6] = (byte) (sequence
& 0xFF);
431 bytes
[7] = (byte) ((int) sequence
>> 8);
432 Buffer
.BlockCopy (data
, 0, bytes
, 8, data
.Length
);
434 ushort checksum
= ComputeChecksum (bytes
);
435 bytes
[2] = (byte) (checksum
& 0xFF);
436 bytes
[3] = (byte) ((int) checksum
>> 8);
440 get { return bytes [0]; }
444 get { return bytes [1]; }
447 public ushort Identifier
{
448 get { return (ushort) (bytes [4] + (bytes [5] << 8)); }
451 public ushort Sequence
{
452 get { return (ushort) (bytes [6] + (bytes [7] << 8)); }
455 public byte [] Data
{
457 byte [] data
= new byte [bytes
.Length
- 8];
458 Buffer
.BlockCopy (bytes
, 8, data
, 0, data
.Length
);
463 public byte [] GetBytes ()
468 static ushort ComputeChecksum (byte [] data
)
471 for (int i
= 0; i
< data
.Length
; i
+= 2) {
472 ushort us
= i
+ 1 < data
.Length
? data
[i
+ 1] : (byte) 0;
477 ret
= (ret
>> 16) + (ret
& 0xFFFF);
478 return (ushort) ~ ret
;
481 public IPStatus IPStatus
{
485 return IPStatus
.Success
;
486 case 3: // destination unreacheable
489 return IPStatus
.DestinationNetworkUnreachable
;
491 return IPStatus
.DestinationHostUnreachable
;
493 return IPStatus
.DestinationProtocolUnreachable
;
495 return IPStatus
.DestinationPortUnreachable
;
497 return IPStatus
.BadOption
; // FIXME: likely wrong
499 return IPStatus
.BadRoute
; // not sure if it is correct
505 return IPStatus
.TimeExceeded
;
507 return IPStatus
.TtlReassemblyTimeExceeded
;
511 return IPStatus
.ParameterProblem
;
513 return IPStatus
.SourceQuench
;
515 return IPStatus
.Success
;
517 return IPStatus
.Unknown
;
518 //throw new NotSupportedException (String.Format ("Unexpected pair of ICMP message type and code: type is {0} and code is {1}", Type, Code));
523 private string BuildPingArgs (IPAddress address
, int timeout
, PingOptions options
)
525 CultureInfo culture
= CultureInfo
.InvariantCulture
;
526 StringBuilder args
= new StringBuilder ();
527 uint t
= Convert
.ToUInt32 (Math
.Floor ((timeout
+ 1000) / 1000.0));
528 bool is_mac
= Platform
.IsMacOS
;
530 args
.AppendFormat (culture
, "-q -n -c {0} -w {1} -t {2} -M ", DefaultCount
, t
, options
.Ttl
);
532 args
.AppendFormat (culture
, "-q -n -c {0} -t {1} -o -m {2} ", DefaultCount
, t
, options
.Ttl
);
534 args
.Append (options
.DontFragment
? "do " : "dont ");
535 else if (options
.DontFragment
)
538 args
.Append (address
.ToString ());
540 return args
.ToString ();
543 public Task
<PingReply
> SendPingAsync (IPAddress address
, int timeout
, byte [] buffer
)
545 return SendPingAsync (address
, default_timeout
, default_buffer
, new PingOptions ());
548 public Task
<PingReply
> SendPingAsync (IPAddress address
, int timeout
)
550 return SendPingAsync (address
, default_timeout
, default_buffer
);
553 public Task
<PingReply
> SendPingAsync (IPAddress address
)
555 return SendPingAsync (address
, default_timeout
);
558 public Task
<PingReply
> SendPingAsync (string hostNameOrAddress
, int timeout
, byte [] buffer
)
560 return SendPingAsync (hostNameOrAddress
, timeout
, buffer
, new PingOptions ());
563 public Task
<PingReply
> SendPingAsync (string hostNameOrAddress
, int timeout
, byte [] buffer
, PingOptions options
)
565 IPAddress address
= Dns
.GetHostEntry (hostNameOrAddress
).AddressList
[0];
566 return SendPingAsync (address
, timeout
, buffer
, options
);
569 public Task
<PingReply
> SendPingAsync (string hostNameOrAddress
, int timeout
)
571 return SendPingAsync (hostNameOrAddress
, timeout
, default_buffer
);
574 public Task
<PingReply
> SendPingAsync (string hostNameOrAddress
)
576 return SendPingAsync (hostNameOrAddress
, default_timeout
);
579 public Task
<PingReply
> SendPingAsync (IPAddress address
, int timeout
, byte [] buffer
, PingOptions options
)
581 if ((worker
!= null) || (cts
!= null))
582 throw new InvalidOperationException ("Another SendAsync operation is in progress");
584 var task
= Task
<PingReply
>.Factory
.StartNew (
585 () => Send (address
, timeout
, buffer
, options
), cts
.Token
);
587 task
.ContinueWith ((t
) => {
589 OnPingCompleted (new PingCompletedEventArgs (null, true, null, null));
590 else if (t
.IsFaulted
)
591 OnPingCompleted (new PingCompletedEventArgs (t
.Exception
, false, null, null));
593 OnPingCompleted (new PingCompletedEventArgs (null, false, null, t
.Result
));