context: //
  // Mono.Net.Dns.SimpleResolver
  //
  // Authors:
  //	Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
  //
  // Copyright 2011 Gonzalo Paniagua Javier
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  // http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  //
  using System;
  using System.Collections.Generic;
  using System.Collections.ObjectModel;
  using System.Diagnostics;
  using System.Net;
  using System.Net.Sockets;
  using System.Net.NetworkInformation;
  using System.Text;
  using System.Threading;
  
  namespace Mono.Net.Dns {
  	sealed class SimpleResolver : IDisposable {
  		static string [] EmptyStrings = new string [0];
  		static IPAddress [] EmptyAddresses = new IPAddress [0];
  		IPEndPoint [] endpoints;
  		Socket client;
  		Dictionary<int, SimpleResolverEventArgs> queries;
  		AsyncCallback receive_cb;
  		TimerCallback timeout_cb;
  		bool disposed;
  #if REUSE_RESPONSES
  		Stack<DnsResponse> responses_avail = new Stack<DnsResponse> ();
  #endif
  
  		public SimpleResolver ()
  		{
  			queries = new Dictionary<int, SimpleResolverEventArgs> ();
  			receive_cb = new AsyncCallback (OnReceive);
  			timeout_cb = new TimerCallback (OnTimeout);
  			InitFromSystem ();
  			InitSocket ();
  		}
  
  		void IDisposable.Dispose ()
  		{
  			if (!disposed) {
  				disposed = true;
  				if (client != null) {
  					client.Close ();
  					client = null;
  				}
  			}
  		}
  
  		public void Close ()
  		{
  			((IDisposable) this).Dispose ();
  		}
  
  		void GetLocalHost (SimpleResolverEventArgs args)
  		{
  			//FIXME
  			IPHostEntry entry = new IPHostEntry ();
  			entry.HostName = "localhost";
  			entry.AddressList = new IPAddress [] { IPAddress.Loopback };
  			entry.Aliases = EmptyStrings;
  			args.ResolverError = 0;
  			args.HostEntry = entry;
  			return;
  
  /*
  			List<IPEndPoint> eps = new List<IPEndPoint> ();
  			foreach (NetworkInterface iface in NetworkInterface.GetAllNetworkInterfaces ()) {
  				if (NetworkInterfaceType.Loopback == iface.NetworkInterfaceType)
  					continue;
  
  				foreach (IPAddress addr in iface.GetIPProperties ().DnsAddresses) {
  					if (AddressFamily.InterNetworkV6 == addr.AddressFamily)
  						continue;
  					IPEndPoint ep = new IPEndPoint (addr, 53);
  					if (eps.Contains (ep))
  						continue;
  
  					eps.Add (ep);
  				}
  			}
  			endpoints = eps.ToArray ();
  */
  		}
  
  		// Type A query
  		// Might fill in Aliases
  		// -IPAddress -> return the same IPAddress
  		// -"" -> Local host ip addresses (filter out IPv6 if needed)
  		public bool GetHostAddressesAsync (SimpleResolverEventArgs args)
  		{
  			if (args == null)
  				throw new ArgumentNullException ("args");
  
  			if (args.HostName == null)
  				throw new ArgumentNullException ("args.HostName is null");
  
  			if (args.HostName.Length > 255)
  				throw new ArgumentException ("args.HostName is too long");
  
  			args.Reset (ResolverAsyncOperation.GetHostAddresses);
  			string host = args.HostName;
  			if (host == "") {
  				GetLocalHost (args);
  				return false;
  			}
  			IPAddress addr;
  			if (IPAddress.TryParse (host, out addr)) {
  				IPHostEntry entry = new IPHostEntry ();
  				entry.HostName = host;
  				entry.Aliases = EmptyStrings;
  				entry.AddressList = new IPAddress [1] { addr };
  				args.HostEntry = entry;
  				return false;
  			}
  
  			SendAQuery (args, true);
  			return true;
  		}
  
  		// For names -> type A Query
  		// For IP addresses -> PTR + A -> will at least return itself
  		//	Careful: for IP addresses with PTR, the hostname might yield different IP addresses!
  		public bool GetHostEntryAsync (SimpleResolverEventArgs args)
  		{
  			if (args == null)
  				throw new ArgumentNullException ("args");
  
  			if (args.HostName == null)
  				throw new ArgumentNullException ("args.HostName is null");
  
  			if (args.HostName.Length > 255)
  				throw new ArgumentException ("args.HostName is too long");
  
  			args.Reset (ResolverAsyncOperation.GetHostEntry);
  			string host = args.HostName;
  			if (host == "") {
  				GetLocalHost (args);
  				return false;
  			}
  
  			IPAddress addr;
  			if (IPAddress.TryParse (host, out addr)) {
  				IPHostEntry entry = new IPHostEntry ();
  				entry.HostName = host;
  				entry.Aliases = EmptyStrings;
  				entry.AddressList = new IPAddress [1] { addr };
  				args.HostEntry = entry;
  				args.PTRAddress = addr;
  				SendPTRQuery (args, true);
  				return true;
  			}
  
  			// 3. For IP addresses:
  			//	3.1 Parsing IP succeeds
  			//	3.2 Reverse lookup of the IP fills in HostName -> fails? HostName = IP
  			//	3.3 The hostname resulting from this is used to query DNS again to get the IP addresses
  			//
  			// Exclude IPv6 addresses if not supported by the system
  			// .Aliases is always empty
  			// Length > 255
  			SendAQuery (args, true);
  			return true;
  		}
  
  		bool AddQuery (DnsQuery query, SimpleResolverEventArgs args)
  		{
  			lock (queries) {
  				if (queries.ContainsKey (query.Header.ID))
  					return false;
  				queries [query.Header.ID] = args;
  			}
  			return true;
  		}
  
  		static DnsQuery GetQuery (string host, DnsQType q, DnsQClass c)
  		{
  			return new DnsQuery (host, q, c);
  		}
  
  		void SendAQuery (SimpleResolverEventArgs args, bool add_it)
  		{
  			SendAQuery (args, args.HostName, add_it);
  		}
  
  		void SendAQuery (SimpleResolverEventArgs args, string host, bool add_it)
  		{
  			DnsQuery query = GetQuery (host, DnsQType.A, DnsQClass.IN);
  			SendQuery (args, query, add_it);
  		}
  
  		static string GetPTRName (IPAddress address)
  		{
  			// TODO: IPv6 PTR query?
  			byte [] bytes = address.GetAddressBytes ();
  			// "XXX.XXX.XXX.XXX.in-addr.arpa".Length
  			StringBuilder sb = new StringBuilder (28);
  			for (int i = bytes.Length - 1; i >= 0; i--) {
  				sb.AppendFormat ("{0}.", bytes [i]);
  			}
  			sb.Append ("in-addr.arpa");
  			return sb.ToString ();
  		}
  
  		void SendPTRQuery (SimpleResolverEventArgs args, bool add_it)
  		{
  			DnsQuery query = GetQuery (GetPTRName (args.PTRAddress), DnsQType.PTR, DnsQClass.IN);
  			SendQuery (args, query, add_it);
  		}
  
  		void SendQuery (SimpleResolverEventArgs args, DnsQuery query, bool add_it)
  		{
  			// TODO: not sure about reusing IDs when add_it == false
  			int count = 0;
  			if (add_it) {
  				do {
  					query.Header.ID = (ushort)new Random().Next(1, 65534);
  					if (count > 500)
  						throw new InvalidOperationException ("Too many pending queries (or really bad luck)");
  				} while (AddQuery (query, args) == false);
  				args.QueryID = query.Header.ID;
  			} else {
  				query.Header.ID = args.QueryID;
  			}
  			if (args.Timer == null)
  				args.Timer = new Timer (timeout_cb, args, 5000, Timeout.Infinite);
  			else
  				args.Timer.Change (5000, Timeout.Infinite);
  			client.BeginSend (query.Packet, 0, query.Length, SocketFlags.None, null, null);
  		}
  
  		byte [] GetFreshBuffer ()
  		{
  #if !REUSE_RESPONSES
  			return new byte [512];
  #else
  
  			DnsResponse response = null;
  			lock (responses_avail) {
  				if (responses_avail.Count > 0) {
  					response = responses_avail.Pop ();
  				}
  			}
  			if (response == null) {
  				response = new DnsResponse ();
  			} else {
  				response.Reset ();
  			}
  			return response;
  #endif
  		}
  
  		void FreeBuffer (byte [] buffer)
  		{
  #if REUSE_RESPONSES
  			// TODO: set some limit here. Configurable?
  			lock (responses_avail) {
  				responses_avail.Push (response);
  			}
  #endif
  		}
  
  		void InitSocket ()
  		{
  			client = new Socket (AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  			client.Blocking = true;
  			client.Bind (new IPEndPoint (IPAddress.Any, 0));
  
gt: 			client.Connect (endpoints [0]);