1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2  * Copyright (c) 2003-2012 by AG-Software 											 *
3  * All Rights Reserved.																 *
4  * Contact information for AG-Software is available at http://www.ag-software.de	 *
5  *																					 *
6  * Licence:																			 *
7  * The agsXMPP SDK is released under a dual licence									 *
8  * agsXMPP can be used under either of two licences									 *
9  * 																					 *
10  * A commercial licence which is probably the most appropriate for commercial 		 *
11  * corporate use and closed source projects. 										 *
12  *																					 *
13  * The GNU Public License (GPL) is probably most appropriate for inclusion in		 *
14  * other open source projects.														 *
15  *																					 *
16  * See README.html for details.														 *
17  *																					 *
18  * For general enquiries visit our website at:										 *
19  * http://www.ag-software.de														 *
20  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
21 
22 //
23 // Bdev.Net.Dns by Rob Philpott, Big Developments Ltd. Please send all bugs/enhancements to
24 // rob@bigdevelopments.co.uk  This file and the code contained within is freeware and may be
25 // distributed and edited without restriction.
26 //
27 
28 using System;
29 using System.Collections;
30 using System.Net;
31 using System.Net.Sockets;
32 
33 namespace agsXMPP.Net.Dns
34 {
35 	/// <summary>
36 	/// Summary description for Dns.
37 	/// </summary>
38 	public sealed class Resolver
39 	{
40 		const   int		_dnsPort            = 53;
41 		const   int		_udpRetryAttempts   = 2;
42 		static  int		_uniqueId;
43         const   int     _udpTimeout         = 1000;
44 
45 		/// <summary>
46 		/// Private constructor - this static class should never be instantiated
47 		/// </summary>
Resolver()48         private Resolver()
49         {
50             // no implementation
51         }
52 
53         /// <summary>
54         /// Shorthand form to make SRV querying easier, essentially wraps up the retreival
55         /// of the SRV records, and sorts them by preference
56         /// </summary>
57         /// <param name="domain">domain name to retreive SRV RRs for</param>
58         /// <param name="dnsServer">the server we're going to ask</param>
59         /// <returns>An array of SRVRecords</returns>
SRVLookup(string domain, IPAddress dnsServer)60         public static SRVRecord[] SRVLookup(string domain, IPAddress dnsServer)
61         {
62             // check the inputs
63             if (domain == null) throw new ArgumentNullException("domain");
64             if (dnsServer == null)  throw new ArgumentNullException("dnsServer");
65 
66             // create a request for this
67             Request request = new Request();
68 
69             // add one question - the SRV IN lookup for the supplied domain
70             request.AddQuestion(new Question(domain, DnsType.SRV, DnsClass.IN));
71 
72             // fire it off
73             Response response = Lookup(request, dnsServer);
74 
75             // if we didn't get a response, then return null
76             if (response == null) return null;
77 
78             // create a growable array of SRV records
79             ArrayList resourceRecords = new ArrayList();
80 
81             // add each of the answers to the array
82             foreach (Answer answer in response.Answers)
83             {
84                 // if the answer is an SRV record
85                 if (answer.Type == DnsType.SRV)
86                 {
87                    // add it to our array
88                    resourceRecords.Add(answer.Record);
89                 }
90             }
91 
92             // create array of MX records
93             SRVRecord[] srvRecords = new SRVRecord[resourceRecords.Count];
94 
95             // copy from the array list
96             resourceRecords.CopyTo(srvRecords);
97 
98             // sort into lowest preference order
99             Array.Sort(srvRecords);
100 
101             // and return
102             return srvRecords;
103         }
104 
105         /// <summary>
106 		/// The principal look up function, which sends a request message to the given
107 		/// DNS server and collects a response. This implementation re-sends the message
108 		/// via UDP up to two times in the event of no response/packet loss
109 		/// </summary>
110 		/// <param name="request">The logical request to send to the server</param>
111 		/// <param name="dnsServer">The IP address of the DNS server we are querying</param>
112 		/// <returns>The logical response from the DNS server or null if no response</returns>
Lookup(Request request, IPAddress dnsServer)113 		public static Response Lookup(Request request, IPAddress dnsServer)
114 		{
115 			// check the inputs
116 			if (request == null) throw new ArgumentNullException("request");
117 			if (dnsServer == null) throw new ArgumentNullException("dnsServer");
118 
119 			// We will not catch exceptions here, rather just refer them to the caller
120 
121 			// create an end point to communicate with
122 			IPEndPoint server = new IPEndPoint(dnsServer, _dnsPort);
123 
124 			// get the message
125 			byte[] requestMessage = request.GetMessage();
126 
127 			// send the request and get the response
128 			byte[] responseMessage = UdpTransfer(server, requestMessage);
129 
130 			// and populate a response object from that and return it
131 			return new Response(responseMessage);
132 		}
133 
UdpTransfer(IPEndPoint server, byte[] requestMessage)134 		private static byte[] UdpTransfer(IPEndPoint server, byte[] requestMessage)
135 		{
136 			// UDP can fail - if it does try again keeping track of how many attempts we've made
137 			int attempts = 0;
138 
139 			// try repeatedly in case of failure
140 			while (attempts <= _udpRetryAttempts)
141 			{
142 				// firstly, uniquely mark this request with an id
143 				unchecked
144 				{
145 					// substitute in an id unique to this lookup, the request has no idea about this
146 					requestMessage[0] = (byte)(_uniqueId >> 8);
147 					requestMessage[1] = (byte)_uniqueId;
148 				}
149 
150 				// we'll be send and receiving a UDP packet
151 				Socket socket;
152                 if (Socket.OSSupportsIPv6 && (server.AddressFamily == AddressFamily.InterNetworkV6))
153                     socket = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp); // V6
154                 else
155                     socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
156 
157 				// we will wait at most 1 second for a dns reply
158 				socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, _udpTimeout);
159 
160 				// send it off to the server
161 				socket.SendTo(requestMessage, requestMessage.Length, SocketFlags.None, server);
162 
163 				// RFC1035 states that the maximum size of a UDP datagram is 512 octets (bytes)
164 				byte[] responseMessage = new byte[512];
165 
166 				try
167 				{
168 					// wait for a response upto 1 second
169 					socket.Receive(responseMessage);
170 
171 					// make sure the message returned is ours
172 					if (responseMessage[0] == requestMessage[0] && responseMessage[1] == requestMessage[1])
173 					{
174 						// its a valid response - return it, this is our successful exit point
175 						return responseMessage;
176 					}
177 				}
178 				catch (SocketException)
179 				{
180 					// failure - we better try again, but remember how many attempts
181 					attempts++;
182 				}
183 				finally
184 				{
185 					// increase the unique id
186 					_uniqueId++;
187 
188 					// close the socket
189 					socket.Close();
190 				}
191 			}
192 
193 			// the operation has failed, this is our unsuccessful exit point
194 			throw new NoResponseException();
195 		}
196 	}
197 }
198