using System; using System.Collections.Generic; using System.Linq; using System.Text; using Tor.Controller; using System.Text.RegularExpressions; using System.Diagnostics; namespace Tor.Status { /// /// A class containing methods and properties for reading the status of the tor network service. /// [DebuggerStepThrough] public sealed class Status : MarshalByRefObject { private const int MaximumRecords = 30; private readonly List circuits; private readonly Client client; private readonly List orConnections; private readonly List streams; private readonly object synchronizeCircuits; private readonly object synchronizeORConnections; private readonly object synchronizeStreams; /// /// Initializes a new instance of the class. /// /// The client for which this object instance belongs. internal Status(Client client) { this.circuits = new List(); this.client = client; this.orConnections = new List(); this.streams = new List(); this.synchronizeCircuits = new object(); this.synchronizeORConnections = new object(); this.synchronizeStreams = new object(); } #region Properties /// /// Gets the current circuits configured against the tor client. /// public CircuitCollection Circuits { get { lock (synchronizeCircuits) return new CircuitCollection(circuits); } } /// /// Gets a value indicating whether the tor software service is dormant. A value of true indicates that /// the tor network has not been active for a while, or is dormant for some other reason. /// public bool IsDormant { get { return PropertyGetIsDormant(); } } /// /// Gets the current OR connections configured against the tor client. /// public ORConnectionCollection ORConnections { get { lock (synchronizeORConnections) return new ORConnectionCollection(orConnections); } } /// /// Gets the current streams configured against the tor client. /// public StreamCollection Streams { get { lock (synchronizeStreams) return new StreamCollection(streams); } } /// /// Gets an approximation of the total bytes downloaded by the tor software. /// public Bytes TotalBytesDownloaded { get { return PropertyGetTotalBytesDownloaded(); } } /// /// Gets an approximation of the total bytes uploaded by the tor software. /// public Bytes TotalBytesUploaded { get { return PropertyGetTotalBytesUploaded(); } } /// /// Gets the version number of the running tor application associated with this client. /// public Version Version { get { return PropertyGetVersion(); } } #endregion #region Events /// /// Occurs when the bandwidth download and upload values change within the tor service. These values are a report of the /// download and upload rates within the last second. /// public event BandwidthEventHandler BandwidthChanged { add { client.Events.BandwidthChanged += value; } remove { client.Events.BandwidthChanged -= value; } } /// /// Occurs when the circuits have been updated. /// public event EventHandler CircuitsChanged; /// /// Occurs when the OR connections have been updated. /// public event EventHandler ORConnectionsChanged; /// /// Occurs when the streams have been updated. /// public event EventHandler StreamsChanged; #endregion #region Tor.Events.Events /// /// Called when a circuit changes within the tor service. /// /// The sender. /// The instance containing the event data. private void OnCircuitChanged(object sender, CircuitEventArgs e) { if (e.Circuit == null) return; lock (synchronizeCircuits) { Circuit existing = circuits.Where(c => c.ID == e.Circuit.ID).FirstOrDefault(); if (existing == null) { if (Status.MaximumRecords <= circuits.Count) { Circuit removal = circuits.Where(c => c.Status == CircuitStatus.Closed || c.Status == CircuitStatus.Failed).FirstOrDefault(); if (removal == null) return; circuits.Remove(removal); } existing = e.Circuit; existing.GetRouters(); circuits.Add(existing); } else { bool update = existing.Paths.Count != e.Circuit.Paths.Count || e.Circuit.Status == CircuitStatus.Extended; existing.BuildFlags = e.Circuit.BuildFlags; existing.HSState = e.Circuit.HSState; existing.Paths = e.Circuit.Paths; existing.Purpose = e.Circuit.Purpose; existing.Reason = e.Circuit.Reason; existing.Status = e.Circuit.Status; existing.TimeCreated = e.Circuit.TimeCreated; if (update) existing.GetRouters(); } } if (CircuitsChanged != null) CircuitsChanged(client, EventArgs.Empty); } /// /// Called when an OR connection changes within the tor service. /// /// The sender. /// The instance containing the event data. private void OnORConnectionChanged(object sender, ORConnectionEventArgs e) { if (e.Connection == null) return; lock (synchronizeORConnections) { ORConnection existing; if (e.Connection.ID != 0) existing = orConnections.Where(o => o.ID == e.Connection.ID).FirstOrDefault(); else existing = orConnections.Where(o => o.Target.Equals(e.Connection.Target, StringComparison.CurrentCultureIgnoreCase)).FirstOrDefault(); if (existing == null) { if (Status.MaximumRecords <= orConnections.Count) { ORConnection removal = orConnections.Where(o => o.Status == ORStatus.Closed || o.Status == ORStatus.Failed).FirstOrDefault(); if (removal == null) return; orConnections.Remove(removal); } existing = e.Connection; orConnections.Add(existing); } else { existing.CircuitCount = e.Connection.CircuitCount; existing.ID = e.Connection.ID; existing.Reason = e.Connection.Reason; existing.Status = e.Connection.Status; existing.Target = e.Connection.Target; } } if (ORConnectionsChanged != null) ORConnectionsChanged(client, EventArgs.Empty); } /// /// Called when a stream changes within the tor service. /// /// The sender. /// The instance containing the event data. private void OnStreamChanged(object sender, StreamEventArgs e) { if (e.Stream == null) return; lock (synchronizeStreams) { Stream existing = streams.Where(s => s.ID == e.Stream.ID).FirstOrDefault(); if (existing == null) { if (Status.MaximumRecords <= streams.Count) { Stream removal = streams.Where(s => s.Status == StreamStatus.Closed || s.Status == StreamStatus.Failed).FirstOrDefault(); if (removal == null) return; streams.Remove(removal); } existing = e.Stream; streams.Add(existing); } else { existing.CircuitID = e.Stream.CircuitID; existing.Purpose = e.Stream.Purpose; existing.Reason = e.Stream.Reason; existing.Status = e.Stream.Status; } } if (StreamsChanged != null) StreamsChanged(client, EventArgs.Empty); } #endregion /// /// Gets a read-only collection of all ORs which the tor application has an opinion about. This method can be time, memory /// and CPU intensive, so should be used infrequently. /// /// A object instance containing the router information; otherwise, null if the request fails. public RouterCollection GetAllRouters() { GetAllRouterStatusCommand command = new GetAllRouterStatusCommand(); GetAllRouterStatusResponse response = command.Dispatch(client); if (response.Success) return response.Routers; return null; } /// /// Gets a country code for an IP address. This method will not work unless a geoip and/or geoip6 file has been supplied. /// /// The IP address which should be resolved. /// A containing the country code; otherwise, null if the country code could not be resolved. public string GetCountryCode(string ipAddress) { if (ipAddress == null) throw new ArgumentNullException("router"); GetInfoCommand command = new GetInfoCommand(string.Format("ip-to-country/{0}", ipAddress)); GetInfoResponse response = command.Dispatch(client); if (response.Success) { string[] values = response.Values[0].Split(new[] { '=' }, 2); if (values.Length == 2) return values[1].Trim(); } return null; } /// /// Gets a country code for a router within the tor network. This method will not work unless a geoip and/or geoip6 file has been supplied. /// /// The router to retrieve the country code for. /// A containing the country code; otherwise, null if the country code could not be resolved. public string GetCountryCode(Router router) { if (router == null) throw new ArgumentNullException("router"); string address = router.IPAddress.ToString(); GetInfoCommand command = new GetInfoCommand(string.Format("ip-to-country/{0}", address)); GetInfoResponse response = command.Dispatch(client); if (response.Success) { string[] values = response.Values[0].Split(new[] { '=' }, 2); if (values.Length == 2) return values[1].Trim(); return values[0].Trim().ToUpper(); } return null; } /// /// Gets a value indicating whether the tor software service is dormant. /// /// true if the tor software service is dormant; otherwise, false. private bool PropertyGetIsDormant() { GetInfoCommand command = new GetInfoCommand("dormant"); GetInfoResponse response = command.Dispatch(client); if (!response.Success) return false; int value; if (!int.TryParse(response.Values[0], out value)) return false; return value != 0; } /// /// Gets an approximation of the total bytes downloaded by the tor software. /// /// A object instance containing the estimated number of bytes. private Bytes PropertyGetTotalBytesDownloaded() { GetInfoCommand command = new GetInfoCommand("traffic/read"); GetInfoResponse response = command.Dispatch(client); if (!response.Success) return Bytes.Empty; double value; if (!double.TryParse(response.Values[0], out value)) return Bytes.Empty; return new Bytes(value).Normalize(); } /// /// Gets an approximation of the total bytes uploaded by the tor software. /// /// A object instance containing the estimated number of bytes. private Bytes PropertyGetTotalBytesUploaded() { GetInfoCommand command = new GetInfoCommand("traffic/written"); GetInfoResponse response = command.Dispatch(client); if (!response.Success) return Bytes.Empty; double value; if (!double.TryParse(response.Values[0], out value)) return Bytes.Empty; return new Bytes(value).Normalize(); } /// /// Gets the version of the running tor application. /// /// A object instance containing the version. private Version PropertyGetVersion() { GetInfoCommand command = new GetInfoCommand("version"); GetInfoResponse response = command.Dispatch(client); if (!response.Success) return new Version(); Regex pattern = new Regex(@"(?\d{1,})\.(?\d{1,})\.(?\d{1,})\.(?\d{1,})(?:$|\s)"); Match match = pattern.Match(response.Values[0]); if (match.Success) return new Version( Convert.ToInt32(match.Groups["major"].Value), Convert.ToInt32(match.Groups["minor"].Value), Convert.ToInt32(match.Groups["build"].Value), Convert.ToInt32(match.Groups["revision"].Value) ); return new Version(); } /// /// Starts the status controller listening for changes within the tor client. /// internal void Start() { this.client.Events.CircuitChanged += new CircuitEventHandler(OnCircuitChanged); this.client.Events.ORConnectionChanged += new ORConnectionEventHandler(OnORConnectionChanged); this.client.Events.StreamChanged += new StreamEventHandler(OnStreamChanged); } } }