src/dnsprotocol

Source   Edit  

Domain Name System (DNS) protocol for Nim programming language

The current implementation was based on RFCs 1034 and 1035. There is still much to be done...

This package does not transport data, that is, it is neither a DNS client nor a DNS server, but it can be used to implement them. If you need a client dns use ndns.

Current Support

Most types of the IN class are currently supported. However, if I need to add a type, I would be happy to receive a PR and a little less with an issue.

For dns types, classes, rcodes, etc. that are supported, access here. Unsupported types are stored in RDataUnknown, thus avoiding runtime errors.

Basic Use

Creating a Message object with a QType.A query for the domain name nim-lang.org:

import dnsprotocol

let header = initHeader(id = 12345'u16, rd = true)

let question = initQuestion("nim-lang.org", QType.A, QClass.IN)
  # If the last character of "nim-lang.org" is not a '.', the initializer will
  # add, as it is called the DNS root.

let msg = initMessage(header, @[question])
  # The initializer automatically changes `header.qdcount` to `1'u16`

echo msg

let bmsg = toBinMsg(msg)

echo "\n", bmsg

Creating a Message object with the query response from the previous example:

import dnsprotocol

let header = initHeader(id = 12345'u16, qr = QR.Response, rd = true, ra = true)

let question = initQuestion("nim-lang.org.", QType.A, QClass.IN)

let rr1 = initResourceRecord("nim-lang.org", Type.A, Class.IN, 299'i32, 4'u16,
                             RDataA(address: [172'u8, 67, 132, 242]))
  # If the last character of "nim-lang.org" is not a '.', the initializer will
  # add, as it is called the DNS root.

let rr2 = initResourceRecord("nim-lang.org.", Type.A, Class.IN, 299'i32, 4'u16,
                             RDataA(address: [104'u8, 28, 19, 79]))
  # The `rdlength` parameter does not need a value, as the `toBinMsg()` does not
  # use it. The `toBinMsg()` takes the binary size of `rdata` and writes it to
  # the binary DNS message.

let rr3 = initResourceRecord("nim-lang.org.", Type.A, Class.IN, 299'i32, 4'u16,
                             RDataA(address: [104'u8, 28, 18, 79]))

let msg = initMessage(header, @[question], @[rr1, rr2, rr3])
  # The initializer automatically changes: `header.qdcount` to `1'u16` and
  # `header.ancount` to `3'u16`.

echo repr(msg) # repr() to show RDatas (RDataA)

let bmsg = toBinMsg(msg)

echo "\n", bmsg

Procs

proc initHeader(id: uint16 = 0'u16; qr: QR = QR.Query;
                opcode: OpCode = OpCode.Query; aa: bool = false;
                tc: bool = false; rd: bool = false; ra: bool = false;
                rcode: RCode = RCode.NoError; qdcount: uint16 = 0'u16;
                ancount: uint16 = 0'u16; nscount: uint16 = 0'u16;
                arcount: uint16 = 0'u16): Header {....raises: [], tags: [],
    forbids: [].}

Returns a created Header object.

The header includes fields that specify which of the remaining sections are present, and also specify whether the message is a query or a response, a standard query or some other opcode, etc.

Parameters

  • id is an identifier assigned to any kind of query. This identifier is copied the corresponding reply and can be used to match up replies to outstanding queries.
  • qr specifies whether this message is a query (QR.Query) or a response (QR.Response).
  • opcode specifies kind of query. This value is set by the originator of a query and copied into the response. See OpCode.
  • aa is a parameter valid in responses, and if it is true, specifies that the responding name server is an authority for the domain name in question section.
  • tc, if true, specifies that this message was truncated due to length greater than permitted on the transmission channel.
  • rd is a parameter set in a query and is copied into the response. If it is true, it directs the name server to pursue the query recursively.
  • ra is set in a response. If it is true, denotes whether recursive query support is available in the name server.
  • rcode is a parameter set as part of responses. See RCode.
  • qdcount specifies the number of entries in the question section.
  • ancount specifies the number of resource records in the answer section.
  • nscount specifies the number of name server resource records in the authority records section.
  • arcount specifies the number of resource records in the additional records section.

Notes

  • qdcount, ancount, nscount and arcount do not need to be passed correctly if the Message object is initialized with the initMessage() procedure, which will correct these header fields according to the quantity of items in questions, answers, authorities and additionals.
  • If rcode is an enumerator greater than 15, it will be necessary to create a ResourceRecord of type Type.OPT. This will be done automatically if a Message object is initialized with the initMessage() procedure.
Source   Edit  
proc initMessage(header: Header; questions: Questions = @[];
                 answers: Answers = @[]; authorities: Authorities = @[];
                 additionals: Additionals = @[]): Message {.
    ...raises: [ValueError], tags: [], forbids: [].}

Returns a created Message object.

All communications inside of the DNS protocol are carried in a single format called a message. The top level format of message is divided into 5 sections (some of which are empty in certain cases) shown below:

Parameters

  • header includes fields that specify which of the remaining sections are present, and also specify whether the message is a query or a response, a standard query or some other opcode, etc.
  • question contains zero or more questions for a name server.
  • answers contains zero or more resource records that answer the question.
  • authorities contains zero or more resource records that point toward an authoritative name server.
  • additionals contains zero or more resource records which relate to the query, but are not strictly answers for the question.

Notes

  • Header.qdcount, Header.ancount, Header.nscount and Header.arcount will be defined according to the number of items passed in questions, answers, authorities and additionals, respectively.
  • If Header.flags.rcode is an enumerator greater than 15, the upper 8 bits will be passed through a ResourceRecord of type Type.OPT. When the ResourceRecord of type Type.OPT is not passed in additionals, it will be created automatically.
Source   Edit  
proc initOptRR(udpSize: uint16; extRCode: uint8; version: uint8; do: bool;
               rdlength: uint16; rdata: RDataOPT): ResourceRecord {....raises: [],
    tags: [], forbids: [].}

Returns a ResourceRecord object of type OPT pseudo-RR (meta-RR).

The object created is Type.OPT (41).

An OPT record does not carry any DNS data. It is used only to contain control information pertaining to the question-and-answer sequence of a specific transaction.

Parameters

  • udpSize is UDP payload size.
  • extRCode is the upper 8 bits that allow you to extend the RCode header flag beyond 15.
  • version specifies the adopted version.
  • do if true indicates that it is capable of accepting DNSSEC security RRs. If false indicates that it is not prepared to deal with DNSSEC security RRs.
  • rdlength specifies the length of the rdata.
  • rdata describes the resource, which can contain zero or more OPTOption. See RDataOPT for more details.

Notes

  • rdata can be initialized as nil, but it is not recommended.
  • For more information about OPT RR visit RFC-6891.
  • ResourceRecord.extRCode can be changed if Header.flags.rcode passed in initMessage() has its enumerator with different upper 8 bits.
Source   Edit  
proc initQuestion(qname: string; qtype: QType; qclass: QClass = QClass.IN): Question {.
    ...raises: [], tags: [], forbids: [].}

Returns a created Question object.

The question contains fields that describe a question to a name server.

Parameters

  • qname is a domain name. It can be an empty string ""
  • qtype specifies the type of the query. See QType.
  • qclass specifies the class of the query. See QClass.

Note

  • The last character of qname must always be a '.'. If not, it will be added automatically.
Source   Edit  
proc initResourceRecord(name: string; type: Type; class: Class; ttl: int32;
                        rdlength: uint16; rdata: RDatas): ResourceRecord

Returns a created ResourceRecord object.

The answer, authority, and additional sections all share the same format: a variable number of resource records, where the number of records is specified in the corresponding count field in the header.

Parameters

  • name is an owner name, i.e., the name of the node to which this resource record pertains.
  • type specifies the type of the resource record. See Type.
  • class specifies the class of the resource record. See Class.
  • ttl specifies the time interval that the resource record may be cached before the source of the information should again be consulted.
  • rdlength specifies the length of the rdata.
  • rdata describes the resource. The format of this information varies according to the Type and Class of the resource record. See RDatas.

Notes

  • The last character of name must always be a '.'. If not, it will be added automatically.
  • rdata can be initialized as nil, but it is not recommended.
  • It must not be used to initialize a ResourceRecord of type Type.OPT. To do this, use initOptRR().
Source   Edit  
proc parseMessage(bmsg: BinMsg): Message {.
    ...raises: [IOError, OSError, ValueError], tags: [ReadIOEffect, WriteIOEffect],
    forbids: [].}
Parses a binary DNS protocol message contained in bmsg. Source   Edit  
proc toBinMsg(header: Header; ss: StringStream) {....raises: [IOError, OSError],
    tags: [WriteIOEffect], forbids: [].}

Turns a Header object into a binary DNS protocol message stored in ss.

The use of this procedure is advised for optimization purposes when you know what to do. Otherwise, use toBinMsg.

Source   Edit  
proc toBinMsg(msg: Message; isTcp: bool = false): BinMsg {.
    ...raises: [IOError, OSError, ValueError, KeyError], tags: [WriteIOEffect],
    forbids: [].}
Returns a binary DNS protocol message from the msg. If isTcp is true, the message is prefixed with a two byte length field which gives the message length, excluding the two byte length field. Source   Edit  
proc toBinMsg(question: Question; ss: StringStream;
              dictionary: var Table[string, uint16]) {.
    ...raises: [ValueError, IOError, OSError, KeyError], tags: [WriteIOEffect],
    forbids: [].}

Turns a Question object into a binary DNS protocol message stored in ss.

The use of this procedure is advised for optimization purposes when you know what to do. Otherwise, use toBinMsg.

Source   Edit  
proc toBinMsg(rr: ResourceRecord; ss: StringStream;
              dictionary: var Table[string, uint16]) {.
    ...raises: [ValueError, IOError, OSError, KeyError], tags: [WriteIOEffect],
    forbids: [].}

Turns a ResourceRecord object into a binary DNS protocol message stored in ss.

The use of this procedure is advised for optimization purposes when you know what to do. Otherwise, use toBinMsg.

Source   Edit