using System;
using System.Collections.Generic;
using System.Net;
using System.Net.NetworkInformation;
using System.Text;
using Kaleida.ServiceMonitor.Framework;
using Kaleida.ServiceMonitor.Framework.Responses;
namespace Kaleida.ServiceMonitor.Operations.PreparedRequests
{
public class TraceRoute : PreparedRequest
{
private readonly string hostName;
private readonly TimeSpan timeout;
public TraceRoute(string hostName) : this(hostName, "50s")
{
}
public TraceRoute(string hostName, string timeout)
{
if (hostName == null) throw new ArgumentNullException("hostName");
var timeoutTimeSpan = timeout.ToTimeSpan();
if (timeoutTimeSpan.TotalMilliseconds < 1)
throw new ArgumentOutOfRangeException("timeout", timeout, "Cannot be less than 1 millisecond");
this.hostName = hostName;
this.timeout = timeoutTimeSpan;
}
public override string Description
{
get { return string.Format("Perform a traceroute to {0} (timeout set to {1}ms)", hostName, timeout.TotalMilliseconds); }
}
public override object GetResponse()
{
try
{
var traceRoute = new TraceRouteHelper(timeout);
IList<IPAddress> ipAddresses = traceRoute.GetTraceRoute(hostName);
return new TraceRouteResponse(ipAddresses);
}
catch (PingException exception)
{
return new TraceRouteResponse(exception);
}
catch (PingTimedOutException exception)
{
return new TraceRouteResponse(exception);
}
}
private class TraceRouteHelper
{
private const string Data = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
private TimeSpan timeout;
public TraceRouteHelper(TimeSpan timeout)
{
this.timeout = timeout;
}
public IList<IPAddress> GetTraceRoute(string hostNameOrAddress)
{
return GetTraceRoute(hostNameOrAddress, 1);
}
private IList<IPAddress> GetTraceRoute(string hostNameOrAddress, int ttl)
{
var pinger = new System.Net.NetworkInformation.Ping();
var pingerOptions = new PingOptions(ttl, true);
byte[] buffer = Encoding.ASCII.GetBytes(Data);
PingReply reply = pinger.Send(hostNameOrAddress, (int)timeout.TotalMilliseconds, buffer, pingerOptions);
if (reply == null)
throw new InvalidOperationException("Ping reply was null");
var nodes = new List<IPAddress>();
if (reply.Status == IPStatus.Success)
{
nodes.Add(reply.Address);
}
else if (reply.Status == IPStatus.TtlExpired)
{
nodes.Add(reply.Address);
nodes.AddRange(GetTraceRoute(hostNameOrAddress, ttl + 1));
}
else if (reply.Status == IPStatus.TimedOut)
{
throw new PingTimedOutException(string.Format("Ping to {0} timed out after visiting {1} node(s)", hostNameOrAddress, nodes.Count));
}
else
{
var route = string.Join(" -> ", nodes);
throw new InvalidOperationException(string.Format("Ping to {0} returned {1} after visiting {2} node(s): {3}", hostNameOrAddress, reply.Status, nodes.Count, route));
}
return nodes;
}
}
}
}