예제 #1
0
        /// <summary>
        ///  Parses an Resource Record (RR) in a message starting from an offset.
        ///  Moves forward the offset.
        /// </summary>
        private static void ParseRR(ReadOnlySpan <byte> response, ref int offset, QueryTransport queryTransport)
        {
            string NAME = HandleNAMEinMessage(response, ref offset, queryTransport);

            var bytesTYPE     = response.Slice(offset, 2);
            var bytesCLASS    = response.Slice(offset + 2, 2);
            var bytesTTL      = response.Slice(offset + 4, 4);
            var bytesRDLENGTH = response.Slice(offset + 8, 2);

            UInt16 TYPE       = BinaryPrimitives.ReadUInt16BigEndian(bytesTYPE);
            UInt16 CLASS      = BinaryPrimitives.ReadUInt16BigEndian(bytesCLASS);
            UInt32 TTL        = BinaryPrimitives.ReadUInt32BigEndian(bytesTTL);
            UInt16 RDLENGTH   = BinaryPrimitives.ReadUInt16BigEndian(bytesRDLENGTH);
            var    bytesRDATA = response.Slice(offset + 10, RDLENGTH);

            offset += 10 + RDLENGTH;

            // print info
            Console.WriteLine($@"
NAME: {NAME}
TYPE: {TYPE}
CLASS: {CLASS}
TTL: {TTL}
RDLENGTH: {RDLENGTH}
RDATA: ");
            // parse RDATA based on TYPE
            if (TYPE == 1 || TYPE == 28) // IPv4 or IPv6
            {
                IPAddress address = new IPAddress(bytesRDATA);
                Console.WriteLine($"\t{address}");
            }
            else if (TYPE == 2 || TYPE == 12) // NS or PTR
            {
                string PTRDNAME = NAMEToHostname(bytesRDATA, response, queryTransport);
                Console.WriteLine($"\t{PTRDNAME}");
            }
            else // print raw data
            {
                string RDATA_hex = BitConverter.ToString(bytesRDATA.ToArray());
                Console.WriteLine($"\t{RDATA_hex}");
            }
        }
예제 #2
0
        /// <summary>
        ///  The resolver public interface.
        /// </summary>
        public static void CubeResolver
        (
            string name,
            IPAddress dns,
            QTYPE qTYPE = QTYPE.A,
            QueryTransport queryTransport = QueryTransport.Udp,
            string DoHURI = "https://cloudflare-dns.com/dns-query"
        )
        {
            byte[] QNAME;
            // if name is an IPAddress, do a PTR query
            if (IPAddress.TryParse(name, out IPAddress ip))
            {
                qTYPE = QTYPE.PTR;
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    QNAME = IPv4ToQNAME(ip);
                }
                else
                {
                    QNAME = IPv6ToQNAME(ip);
                }
            }
            else // if name is a hostname
            {
                QNAME = HostnameToQNAME(name);
            }

            byte[] query = MakeQueryDatagram(QNAME, qTYPE, queryTransport);
            if (queryTransport == QueryTransport.Udp)
            {
                if (TryUdpQuery(query, dns, out Span <byte> response))
                {
                    ParseResponse(response, query.Length, qTYPE, queryTransport);
                }
                else // Udp failed, fallback to Tcp
                {
                    // change queryTransport and remake query message
                    Console.WriteLine($"UDP query failed. Fallback to TCP.");
                    queryTransport = QueryTransport.Tcp;
                    query          = MakeQueryDatagram(QNAME, qTYPE, queryTransport);
                }
            }
            if (queryTransport == QueryTransport.Tcp)
            {
                if (TryTcpQuery(query, dns, out Span <byte> response))
                {
                    ParseResponse(response, query.Length, qTYPE, queryTransport);
                }
                else // Tcp failed, fallback to DoT
                {
                    // change queryTransport and remake query message
                    Console.WriteLine($"TCP query failed. Fallback to DNS over TLS.");
                    queryTransport = QueryTransport.DoT;
                    query          = MakeQueryDatagram(QNAME, qTYPE, queryTransport);
                }
            }
            if (queryTransport == QueryTransport.DoT)
            {
                if (TryDoTQuery(query, dns, out Span <byte> response))
                {
                    ParseResponse(response, query.Length, qTYPE, queryTransport);
                }
                else // DoT failed
                {
                    Console.WriteLine($"DNS over TLS query failed.");
                }
            }
            if (queryTransport == QueryTransport.DoH)
            {
                if (TryDoHQuery(query, out Span <byte> response, DoHURI))
                {
                    ParseResponse(response, query.Length, qTYPE, queryTransport);
                }
                else // DoH failed
                {
                    Console.WriteLine($"DNS over HTTPS query failed.");
                }
            }
        }
예제 #3
0
        /// <summary>
        ///  Parses the response message.
        /// </summary>
        private static void ParseResponse(ReadOnlySpan <byte> response, int queryLength, QTYPE qTYPE, QueryTransport queryTransport)
        {
            int offset         = 0,
                tcpMsgLength   = 0,
                questionOffset = 0;

            // Tcp & DoT pre-processing
            if (queryTransport == QueryTransport.Tcp || queryTransport == QueryTransport.DoT)
            {
                // save TcpMsgLength and remove it
                byte[] bytesTcpMsgLength = new byte[4];
                Array.Copy(response.Slice(start: 0, length: 2).ToArray(), 0, bytesTcpMsgLength, 2, 2);
                tcpMsgLength = BinaryPrimitives.ReadInt32BigEndian(bytesTcpMsgLength);
                offset       = 2;
            }

            // read header
            var bytesID          = response.Slice(start: offset, length: 2);
            var bytesHeaderStuff = response.Slice(start: offset + 2, length: 2);

            var bytesQDCOUNT = response.Slice(start: offset + 4, length: 2);
            var bytesANCOUNT = response.Slice(start: offset + 6, length: 2);
            var bytesNSCOUNT = response.Slice(start: offset + 8, length: 2);
            var bytesARCOUNT = response.Slice(start: offset + 10, length: 2);

            // parse header
            int QR     = (bytesHeaderStuff[0] & 0b1000_0000) >> 7;
            int OPCODE = (bytesHeaderStuff[0] & 0b0111_1000) >> 3;
            int AA     = (bytesHeaderStuff[0] & 0b0000_0100) >> 2;
            int TC     = (bytesHeaderStuff[0] & 0b0000_0010) >> 1;
            int RD     = bytesHeaderStuff[0] & 0b0000_0001;
            int RA     = (bytesHeaderStuff[1] & 0b1000_0000) >> 7;
            int Z      = (bytesHeaderStuff[1] & 0b0111_0000) >> 4;
            int RCODE  = bytesHeaderStuff[1] & 0b0000_1111;

            UInt16 QDCOUNT = BinaryPrimitives.ReadUInt16BigEndian(bytesQDCOUNT);
            UInt16 ANCOUNT = BinaryPrimitives.ReadUInt16BigEndian(bytesANCOUNT);
            UInt16 NSCOUNT = BinaryPrimitives.ReadUInt16BigEndian(bytesNSCOUNT);
            UInt16 ARCOUNT = BinaryPrimitives.ReadUInt16BigEndian(bytesARCOUNT);

            // checks

            /*if (bytesID[0] != 0x20
            || bytesID[1] != 0x40
            || QR != 1
            || OPCODE != 0
            || TC != 0
            || RCODE != 0
            || QDCOUNT != 1)
            || {
            ||  Console.WriteLine($"Warning: header checks failed.");
            || }*/

            // read question
            // length of QNAME is unknown
            // look for 0x00
            questionOffset = offset + 12;
            // offset is relative to response
            offset = questionOffset + MemoryExtensions.IndexOf(response.Slice(questionOffset), (byte)0x00);
            if (offset == -1)
            {
                Console.WriteLine("Error parsing response: invalid QNAME.");
                return;
            }
            var bytesQNAME  = response.Slice(start: questionOffset, length: offset - questionOffset + 1);
            var bytesQTYPE  = response.Slice(start: offset + 1, length: 2);
            var bytesQCLASS = response.Slice(start: offset + 3, length: 2);
            // parse question
            string QNAME  = NAMEToHostname(bytesQNAME, response, queryTransport);
            UInt16 QTYPE  = BinaryPrimitives.ReadUInt16BigEndian(bytesQTYPE);
            UInt16 QCLASS = BinaryPrimitives.ReadUInt16BigEndian(bytesQCLASS);

            // print info
            Console.WriteLine($@"Received bytes: {response.Length}

####### HEADER #######
ID: {BinaryPrimitives.ReadUInt16BigEndian(bytesID)}
QR: {QR}
OPCODE: {OPCODE}
AA: {AA}
TC: {TC}
RD: {RD}
RA: {RA}
Z: {Z}
RCODE: {RCODE}
QDCOUNT: {QDCOUNT}
ANCOUNT: {ANCOUNT}
NSCOUNT: {NSCOUNT}
ARCOUNT: {ARCOUNT}

####### QUESTION #######
QNAME: {QNAME}
QTYPE: {QTYPE}
QCLASS: {QCLASS}");

            offset += 5;
            // Parse all RRs
            for (int i = 0; i < ANCOUNT; i++)
            {
                Console.WriteLine($"\n####### ANSWER #{i} #######");
                ParseRR(response, ref offset, queryTransport);
            }
            for (int i = 0; i < NSCOUNT; i++)
            {
                Console.WriteLine($"\n####### AUTHORITY #{i} #######");
                ParseRR(response, ref offset, queryTransport);
            }
            for (int i = 0; i < ARCOUNT; i++)
            {
                Console.WriteLine($"\n####### ADDITIONAL #{i} #######");
                ParseRR(response, ref offset, queryTransport);
            }
        }
예제 #4
0
        /// <summary>
        ///  Finds the NAME in a message starting from an offset.
        ///  Precisely extracts the NAME.
        ///  Translates the NAME to a string.
        ///  Moves forward the offset and returns the string.
        /// </summary>
        private static string HandleNAMEinMessage(ReadOnlySpan <byte> response, ref int offset, QueryTransport queryTransport)
        {
            int oldOffset = offset;
            ReadOnlySpan <byte> bytesNAME;

            if (response[offset] >= 0xc0) // domain name is compressed
            {
                bytesNAME = response.Slice(offset, 2);
                offset   += 2;
            }
            else
            {
                // offset is relative to response
                offset    = offset + MemoryExtensions.IndexOf(response.Slice(offset), (byte)0x00);
                bytesNAME = response.Slice(oldOffset, offset - oldOffset + 1);
                offset++;
            }
            return(NAMEToHostname(bytesNAME, response, queryTransport));
        }
예제 #5
0
        /// <summary>
        ///  Translates the NAME in a DNS response to a hostname string.
        ///  Can handle extra bytes after the actual NAME.
        /// </summary>
        private static string NAMEToHostname(ReadOnlySpan <byte> NAME, ReadOnlySpan <byte> DnsMessage, QueryTransport queryTransport)
        {
            int           pos      = 0;
            StringBuilder hostname = new StringBuilder(256);

            while (NAME[pos] != 0x00)
            {
                // if compressed
                if (NAME[pos] >= 0xc0)
                {
                    // extract offset into a new array
                    var byteOffset = NAME.Slice(pos, 2).ToArray();
                    byteOffset[0] &= 0b0011_1111;
                    int offset = BinaryPrimitives.ReadUInt16BigEndian(byteOffset);
                    // recursively get hostname and append to hostname
                    if (queryTransport == QueryTransport.Tcp || queryTransport == QueryTransport.DoT)
                    {
                        offset += 2; // in a Tcp and DoT message, offset is relative to the header ID.
                    }
                    hostname.Append(NAMEToHostname(DnsMessage.Slice(offset, DnsMessage.Length - offset), DnsMessage, queryTransport));
                    hostname.Append('.');
                    // reached the end of NAME
                    break;
                }
                else
                {
                    hostname.Append(Encoding.ASCII.GetString(NAME.Slice(start: pos + 1, length: NAME[pos])));
                    hostname.Append('.');
                    pos += (NAME[pos] + 1);
                }
            }
            // remove the last '.'
            if (hostname.Length != 0)
            {
                hostname.Remove(hostname.Length - 1, 1);
            }
            return(hostname.ToString());
        }
예제 #6
0
        /// <summary>
        ///  Makes a query datagram containing a header and a question.
        ///  Returns a byte array.
        /// </summary>
        private static byte[] MakeQueryDatagram(byte[] QNAME, QTYPE qTYPE, QueryTransport queryTransport)
        {
            byte[]      query;
            byte[]      header     = new byte[12];
            Span <byte> headerSpan = new Span <byte>(header);

            byte[] question = new byte[QNAME.Length + 4];

            // BEGIN Header
            // ID
            // generate random ID
            rnd.NextBytes(headerSpan.Slice(start: 0, length: 2));
            //   0  1  2  3  4  5  6  7  8  9  0  1  2  3  4  5
            // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
            // |QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
            header[2] = 0x01; // RD == 1
            header[3] = 0x00;
            // QDCOUNT
            // No support for multiple questions in one query
            // as it's not supported by any DNS server implementation
            header[4] = 0x00;
            header[5] = 0x01;
            // ANCOUNT
            header[6] = 0x00;
            header[7] = 0x00;
            // NSCOUNT
            header[8] = 0x00;
            header[9] = 0x00;
            // ARCOUNT
            header[10] = 0x00;
            header[11] = 0x00;
            // END Header

            // BEGIN Question
            Array.Copy(QNAME, question, QNAME.Length);
            // QTYPE
            byte[] qTYPE4Bytes = new byte[4];
            BinaryPrimitives.WriteInt32BigEndian(qTYPE4Bytes, (int)qTYPE);
            question[QNAME.Length]     = qTYPE4Bytes[2];
            question[QNAME.Length + 1] = qTYPE4Bytes[3];
            // QCLASS: IN
            question[QNAME.Length + 2] = 0x00;
            question[QNAME.Length + 3] = 0x01;
            // END Question

            // make query message based on queryTransport
            if (queryTransport == QueryTransport.Udp || queryTransport == QueryTransport.DoH) // Udp || DoH
            {
                query = new byte[header.Length + question.Length];
                Array.Copy(header, query, header.Length);
                Array.Copy(question, 0, query, header.Length, question.Length);
            }
            else // Tcp || DoT
            {
                query = new byte[2 + header.Length + question.Length];
                // Tcp Message Length
                byte[] tcpMsgLengthBytes = new byte[4];
                BinaryPrimitives.WriteInt32BigEndian(tcpMsgLengthBytes, header.Length + question.Length);
                Array.Copy(tcpMsgLengthBytes, 2, query, 0, 2);
                Array.Copy(header, 0, query, 2, 12);                 // header.Length == 12
                Array.Copy(question, 0, query, 14, question.Length); // 2 + 12 == 14
            }

            return(query);
        }