/********************************************************************** * Copyright (c) 2010, j. montgomery * * All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * * * + Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * * * + Redistributions in binary form must reproduce the above copyright* * notice, this list of conditions and the following disclaimer * * in the documentation and/or other materials provided with the * * distribution. * * * * + Neither the name of j. montgomery's employer nor the names of * * its contributors may be used to endorse or promote products * * derived from this software without specific prior written * * permission. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS* * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,* * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,* * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED* * OF THE POSSIBILITY OF SUCH DAMAGE. * **********************************************************************/ using System; using System.Diagnostics; using System.IO; using System.Net; using System.Net.Sockets; using System.Net.NetworkInformation; using System.Text; using DnDns.Enums; using DnDns.Records; using DnDns.Security; using System.Threading.Tasks; namespace DnDns.Query { /// /// Summary description for DnsQueryRequest. /// public class DnsQueryRequest : DnsQueryBase { private static Random r = new Random(); private int _bytesSent = 0; private int _socketTimeout = 5000; /// /// The number of bytes sent to query the DNS Server. /// public int BytesSent { get { return _bytesSent; } } /// /// Gets or sets the amount of time in milliseconds that a DnsQueryRequest will wait to receive data once a read operation is initiated. /// Defauts to 5 seconds (5000 ms) /// public int Timeout { get { return _socketTimeout; } set { _socketTimeout = value; } } #region Constructors public DnsQueryRequest() { // Construct the class with some defaults _transactionId = (ushort) r.Next(); _flags = 0; _queryResponse = QueryResponse.Query; this._opCode = OpCode.QUERY; // Recursion Desired this._nsFlags = NsFlags.RD; this._questions = 1; } #endregion Constructors private byte[] BuildQuery(string host) { string newHost; int newLocation = 0; int oldLocation = 0; MemoryStream ms = new MemoryStream(); host = host.Trim(); // decide how to build this query based on type switch (_nsType) { case NsType.PTR: IPAddress queryIP = IPAddress.Parse(host); // pointer should be translated as follows // 209.115.22.3 -> 3.22.115.209.in-addr.arpa char[] ipDelim = new char[] {'.'}; string[] s = host.Split(ipDelim,4); newHost = String.Format("{0}.{1}.{2}.{3}.in-addr.arpa", s[3], s[2], s[1], s[0]); break; default: newHost = host; break; } // Package up the host while(oldLocation < newHost.Length) { newLocation = newHost.IndexOf(".", oldLocation); if (newLocation == -1) newLocation = newHost.Length; byte subDomainLength = (byte)(newLocation - oldLocation); char[] sub = newHost.Substring(oldLocation, subDomainLength).ToCharArray(); ms.WriteByte(subDomainLength); ms.Write(Encoding.ASCII.GetBytes(sub, 0, sub.Length), 0, sub.Length); oldLocation = newLocation + 1; } // Terminate the domain name w/ a 0x00. ms.WriteByte(0x00); return ms.ToArray(); } /// /// /// /// /// /// /// /// public async Task ResolveAsync(string host, NsType queryType, NsClass queryClass, ProtocolType protocol) { return await ResolveAsync(host, queryType, queryClass, protocol, null); } public async Task ResolveAsync(string host, NsType queryType, NsClass queryClass, ProtocolType protocol, TsigMessageSecurityProvider provider) { string dnsServer = string.Empty; dnsServer = "8.8.8.8"; /*// Test for Unix/Linux OS if (Tools.IsPlatformLinuxUnix()) { dnsServer = Tools.DiscoverUnixDnsServerAddress(); } else { IPAddressCollection dnsServerCollection = Tools.DiscoverDnsServerAddresses(); if (dnsServerCollection.Count == 0) throw new Exception("Couldn't detect local DNS Server."); dnsServer = dnsServerCollection[0].ToString(); } if (String.IsNullOrEmpty(dnsServer)) throw new Exception("Couldn't detect local DNS Server."); */ return await ResolveAsync(dnsServer, host, queryType, queryClass, protocol, provider); } /// /// /// /// /// /// /// /// /// The instance of the message security provider to use to secure the DNS request. /// A instance that contains the Dns Answer for the request query. /// /// /// public async Task ResolveAsync(string dnsServer, string host, NsType queryType, NsClass queryClass, ProtocolType protocol, IMessageSecurityProvider messageSecurityProvider) { byte[] bDnsQuery = this.BuildDnsRequest(host, queryType, queryClass, protocol, messageSecurityProvider); // Connect to DNS server and get the record for the current server. IPHostEntry ipe = await Dns.GetHostEntryAsync(dnsServer); IPAddress ipa = ipe.AddressList[0]; IPEndPoint ipep = new IPEndPoint(ipa, (int)UdpServices.Domain); byte[] recvBytes = null; switch (protocol) { case ProtocolType.Tcp: { recvBytes = ResolveTcp(bDnsQuery, ipep); break; } case ProtocolType.Udp: { recvBytes = ResolveUdp(bDnsQuery, ipep); break; } default: { throw new InvalidOperationException("Invalid Protocol: " + protocol); } } //Console.Assert(recvBytes != null, "Failed to retrieve data from the remote DNS server."); if(recvBytes == null) { Logging.AddLogMessage(Logging.LoggingType.ERROR, "Failed to retrieve data from the remote DNS server."); } DnsQueryResponse dnsQR = new DnsQueryResponse(); dnsQR.ParseResponse(recvBytes, protocol); return dnsQR; } private byte[] ResolveUdp(byte[] bDnsQuery, IPEndPoint ipep) { // UDP messages, data size = 512 octets or less //UdpClient udpClient = new UdpClient(); Socket udp = new Socket(SocketType.Dgram, ProtocolType.Udp); byte[] recvBytes = null; try { udp.ReceiveTimeout = _socketTimeout; //udpClient.Client.ReceiveTimeout = _socketTimeout; //udpClient.ConnectAsync(ipep); udp.Connect(ipep); //udpClient.Send(bDnsQuery, bDnsQuery.Length); udp.Send(bDnsQuery); //recvBytes = udpClient.Receive(ref ipep); recvBytes = new byte[udp.ReceiveBufferSize]; udp.Receive(recvBytes); } finally { //udpClient.Close(); } return recvBytes; } private static byte[] ResolveTcp(byte[] bDnsQuery, IPEndPoint ipep) { Socket tcp = new Socket(SocketType.Stream, ProtocolType.Tcp); //TcpClient tcpClient = new TcpClient(); byte[] recvBytes = null; try { tcp.Connect(ipep); //tcpClient.ConnectAsync(ipep); //NetworkStream ns = tcp.Receive //NetworkStream netStream = tcpClient.GetStream(); //BinaryReader netReader = new System.IO.BinaryReader(netStream); //netStream.Write(bDnsQuery, 0, bDnsQuery.Length); tcp.Send(bDnsQuery); // wait until data is avail //while (!netStream.DataAvailable) ; while (tcp.Available < 1) ; //if (tcpClient.Connected && netStream.DataAvailable) if (tcp.Connected && tcp.Available > 1) { // Read first two bytes to find out the length of the response byte[] bLen = new byte[2]; // NOTE: The order of the next two lines matter. Do not reorder // Array indexes are also intentionally reversed tcp.Receive(bLen, bLen.Length, SocketFlags.None); //bLen[1] = (byte)netStream.ReadByte(); //bLen[0] = (byte)netStream.ReadByte(); UInt16 length = BitConverter.ToUInt16(bLen, 0); recvBytes = new byte[length]; tcp.Receive(recvBytes, length, SocketFlags.None); //netStream.Read(recvBytes, 0, length); } } catch (Exception e) { Logging.AddException(e); } return recvBytes; } private byte[] BuildDnsRequest(string host, NsType queryType, NsClass queryClass, ProtocolType protocol, IMessageSecurityProvider messageSecurityProvider) { // Combind the NsFlags with our constant flags ushort flags = (ushort)((ushort)_queryResponse | (ushort)_opCode | (ushort)_nsFlags); this._flags = flags; //NOTE: This limits the librarys ablity to issue multiple queries per request. this._nsType = queryType; this._nsClass = queryClass; this._name = host; if(messageSecurityProvider != null) { messageSecurityProvider.SecureMessage(this); } byte[] bDnsQuery = GetMessageBytes(); // Add two byte prefix that contains the packet length per RFC 1035 section 4.2.2 if (protocol == ProtocolType.Tcp) { // 4.2.2. TCP usageMessages sent over TCP connections use server port 53 (decimal). // The message is prefixed with a two byte length field which gives the message // length, excluding the two byte length field. This length field allows the // low-level processing to assemble a complete message before beginning to parse // it. int len = bDnsQuery.Length; Array.Resize(ref bDnsQuery, len + 2); Array.Copy(bDnsQuery, 0, bDnsQuery, 2, len); bDnsQuery[0] = (byte)((len >> 8) & 0xFF); bDnsQuery[1] = (byte)((len & 0xFF)); } return bDnsQuery; } internal byte[] GetMessageBytes() { MemoryStream memoryStream = new MemoryStream(); byte[] data = new byte[2]; data = BitConverter.GetBytes((ushort)(IPAddress.HostToNetworkOrder(_transactionId) >> 16)); memoryStream.Write(data, 0, data.Length); data = BitConverter.GetBytes((ushort)(IPAddress.HostToNetworkOrder(_flags) >> 16)); memoryStream.Write(data, 0, data.Length); data = BitConverter.GetBytes((ushort)(IPAddress.HostToNetworkOrder(_questions) >> 16)); memoryStream.Write(data, 0, data.Length); data = BitConverter.GetBytes((ushort)(IPAddress.HostToNetworkOrder(_answerRRs) >> 16)); memoryStream.Write(data, 0, data.Length); data = BitConverter.GetBytes((ushort)(IPAddress.HostToNetworkOrder(_authorityRRs) >> 16)); memoryStream.Write(data, 0, data.Length); data = BitConverter.GetBytes((ushort)(IPAddress.HostToNetworkOrder(_additionalRecords.Count) >> 16)); memoryStream.Write(data, 0, data.Length); data = DnsHelpers.CanonicaliseDnsName(_name, false); memoryStream.Write(data, 0, data.Length); data = BitConverter.GetBytes((ushort)(IPAddress.HostToNetworkOrder((ushort)_nsType) >> 16)); memoryStream.Write(data, 0, data.Length); data = BitConverter.GetBytes((ushort)(IPAddress.HostToNetworkOrder((ushort)_nsClass) >> 16)); memoryStream.Write(data, 0, data.Length); foreach (IDnsRecord dnsRecord in AdditionalRRecords) { data = dnsRecord.GetMessageBytes(); memoryStream.Write(data, 0, data.Length); } Logging.AddLogMessage(Logging.LoggingType.INFO, String.Format("The message bytes: {0}", DnsHelpers.DumpArrayToString(memoryStream.ToArray()))); return memoryStream.ToArray(); } } }