@@ -120,6 +120,17 @@ proc initHeader*(id: uint16 = 0'u16, qr: QR = QR.Query,
120120 # # authority records section.
121121 # # - `arcount` specifies the number of resource records in the additional
122122 # # records section.
123+ # #
124+ # # **Notes**
125+ # # - `qdcount`, `ancount`, `nscount` and `arcount` do not need to be passed
126+ # # correctly if the `Message` object is initialized with the `initMessage()`
127+ # # procedure, which will correct these header fields according to the
128+ # # quantity of items in `questions`, `answers`, `authorities` and
129+ # # `additionals`.
130+ # # - If `rcode` is an enumerator greater than 15, it will be necessary to
131+ # # create a `ResourceRecord` of type `Type.OPT`. This will be done
132+ # # automatically if a `Message` object is initialized with the
133+ # # `initMessage()` procedure.
123134 result .id = id
124135
125136 result .flags.qr = qr
@@ -147,6 +158,10 @@ proc initQuestion*(qname: string, qtype: QType, qclass: QClass = QClass.IN):
147158 # # `QType<dnsprotocol/types.html#QType>`_.
148159 # # - `qclass` specifies the class of the query. See
149160 # # `QClass<dnsprotocol/types.html#QClass>`_.
161+ # #
162+ # # **Note**
163+ # # - The last character of `qname` must always be a `'.'`. If not, it will be
164+ # # added automatically.
150165 result .qname = qname
151166
152167 if 0 == len (result .qname) or '.' != result .qname[^ 1 ]:
@@ -177,8 +192,14 @@ proc initResourceRecord*(name: string, `type`: Type, class: Class, ttl: int32,
177192 # # according to the `Type` and `Class` of the resource record. See
178193 # # `RDatas<dnsprotocol/types.html#RDatas>`_.
179194 # #
180- # # **Note**
195+ # # **Notes**
196+ # # - The last character of `name` must always be a `'.'`. If not, it will be
197+ # # added automatically.
181198 # # - `rdata` can be initialized as `nil`, but it is not recommended.
199+ # # - It must not be used to initialize a `ResourceRecord` of type `Type.OPT`.
200+ # # To do this, use `initOptRR()<#initOptRR%2Cuint16%2Cuint8%2Cuint8%2Cbool%2Cuint16%2CRDataOPT>`_.
201+ doAssert (`type` != Type .OPT , " Use `initOptRR()` for `type` == `Type.OPT`" )
202+
182203 result .name = name
183204
184205 if 0 == len (result .name) or '.' != result .name[^ 1 ]:
@@ -190,6 +211,46 @@ proc initResourceRecord*(name: string, `type`: Type, class: Class, ttl: int32,
190211 result .rdlength = rdlength
191212 result .rdata = rdata
192213
214+ proc initOptRR * (udpSize: uint16 , extRCode: uint8 , version: uint8 , `do`: bool ,
215+ rdlength: uint16 , rdata: RDataOPT ): ResourceRecord =
216+ # # Returns a `ResourceRecord` object of type OPT pseudo-RR (meta-RR).
217+ # #
218+ # # The object created is `Type.OPT` (41).
219+ # #
220+ # # An OPT record does not carry any DNS data. It is used only to contain
221+ # # control information pertaining to the question-and-answer sequence of a
222+ # # specific transaction.
223+ # #
224+ # # **Parameters**
225+ # # - `udpSize` is UDP payload size.
226+ # # - `extRCode` is the upper 8 bits that allow you to extend the RCode header
227+ # # flag beyond 15.
228+ # # - `version` specifies the adopted version.
229+ # # - `do` if `true` indicates that it is capable of accepting DNSSEC security
230+ # # RRs. If `false` indicates that it is not prepared to deal with DNSSEC
231+ # # security RRs.
232+ # # - `rdlength` specifies the length of the `rdata`.
233+ # # - `rdata` describes the resource, which can contain zero or more
234+ # # `OPTOption`. See `RDataOPT<dnsprotocol/types.html#RDataOPT>`_ for more
235+ # # details.
236+ # #
237+ # # **Notes**
238+ # # - `rdata` can be initialized as `nil`, but it is not recommended.
239+ # # - For more information about OPT RR visit `RFC-6891<https://www.rfc-editor.org/rfc/rfc6891>`_.
240+ # # - `ResourceRecord.extRCode` can be changed if `Header.flags.rcode` passed
241+ # # in `initMessage()` has its enumerator with different upper 8 bits.
242+ ResourceRecord (
243+ name: " ." ,
244+ `type`: Type .OPT ,
245+ udpSize: udpSize,
246+ extRCode: extRCode,
247+ version: version,
248+ `do`: `do`,
249+ z: 0 'u16 ,
250+ rdlength: rdlength,
251+ rdata: new (RDataOPT )
252+ )
253+
193254proc initMessage * (header: Header , questions: Questions = @ [],
194255 answers: Answers = @ [], authorities: Authorities = @ [],
195256 additionals: Additionals = @ []): Message =
@@ -210,12 +271,35 @@ proc initMessage*(header: Header, questions: Questions = @[],
210271 # # authoritative name server.
211272 # # - `additionals` contains zero or more resource records which relate to the
212273 # # query, but are not strictly answers for the question.
274+ # #
275+ # # **Notes**
276+ # # - `Header.qdcount`, `Header.ancount`, `Header.nscount` and `Header.arcount`
277+ # # will be defined according to the number of items passed in `questions`,
278+ # # `answers`, `authorities` and `additionals`, respectively.
279+ # # - If `Header.flags.rcode` is an enumerator greater than 15, the upper 8
280+ # # bits will be passed through a `ResourceRecord` of type `Type.OPT`. When
281+ # # the `ResourceRecord` of type `Type.OPT` is not passed in `additionals`,
282+ # # it will be created automatically.
213283 result .header = header
214284 result .questions = questions
215285 result .answers = answers
216286 result .authorities = authorities
217287 result .additionals = additionals
218288
289+ let extRCode = uint8 (uint16 (result .header.flags.rcode) shr 4 ) # Will I need an OPT RR?
290+
291+ if extRCode > 0 :
292+ block addOptRR:
293+ for a in mitems (result .additionals):
294+ if a.`type` != Type .OPT : continue
295+
296+ a.extRCode = extRCode
297+
298+ break addOptRR
299+
300+ add (result .additionals, initOptRR (512 'u16 , extRCode, 0 'u8 , false , 0 'u16 ,
301+ new (RDataOPT )))
302+
219303 if len (result .questions) > 65535 :
220304 raise newException (ValueError , " The number of questions exceeds 65535" )
221305
@@ -240,7 +324,7 @@ proc toBinMsg*(header: Header, ss: StringStream) =
240324 # # Turns a `Header` object into a binary DNS protocol message stored in `ss`.
241325 # #
242326 # # The use of this procedure is advised for optimization purposes when you
243- # # know what to do. Otherwise, use `toBinMsg<#toBinMsg,Message,bool>`_
327+ # # know what to do. Otherwise, use `toBinMsg<#toBinMsg,Message,bool>`_.
244328 writeSomeIntBE (ss, header.id)
245329
246330 var a = uint8 (header.flags.qr) shl 7
@@ -255,7 +339,10 @@ proc toBinMsg*(header: Header, ss: StringStream) =
255339 a = uint8 (header.flags.ra) shl 7
256340
257341 a = a or header.flags.z shl 4
258- a = a or uint8 (header.flags.rcode)
342+ a = a or (uint8 (header.flags.rcode) and 0b 1111 ) # Truncates RCode in header
343+ # flags by 15 (4 bits). The
344+ # remainder must be passed
345+ # through OPT RR
259346
260347 writeData (ss, addr a, 1 )
261348
@@ -273,7 +360,7 @@ proc toBinMsg*(question: Question, ss: StringStream,
273360 # # `ss`.
274361 # #
275362 # # The use of this procedure is advised for optimization purposes when you
276- # # know what to do. Otherwise, use `toBinMsg<#toBinMsg,Message,bool>`_
363+ # # know what to do. Otherwise, use `toBinMsg<#toBinMsg,Message,bool>`_.
277364 domainNameToBinMsg (question.qname, ss, dictionary)
278365 writeSomeIntBE (ss, uint16 (question.qtype))
279366 writeSomeIntBE (ss, uint16 (question.qclass))
@@ -284,11 +371,23 @@ proc toBinMsg*(rr: ResourceRecord, ss: StringStream,
284371 # # in `ss`.
285372 # #
286373 # # The use of this procedure is advised for optimization purposes when you
287- # # know what to do. Otherwise, use `toBinMsg<#toBinMsg,Message,bool>`_
374+ # # know what to do. Otherwise, use `toBinMsg<#toBinMsg,Message,bool>`_.
288375 domainNameToBinMsg (rr.name, ss, dictionary)
376+
289377 writeSomeIntBE (ss, uint16 (rr.`type`))
290- writeSomeIntBE (ss, uint16 (rr.class))
291- writeSomeIntBE (ss, rr.ttl)
378+
379+ case rr.`type`
380+ of Type .OPT : # Write an OPT RR
381+ writeSomeIntBE (ss, rr.udpSize)
382+ writeSomeIntBE (ss, rr.extRCode)
383+ writeSomeIntBE (ss, rr.version)
384+
385+ let doAndZ = (uint16 (rr.`do`) shl 15 ) or (rr.z and 0b 0111111111111111 )
386+
387+ writeSomeIntBE (ss, doAndZ)
388+ else :
389+ writeSomeIntBE (ss, uint16 (rr.class))
390+ writeSomeIntBE (ss, rr.ttl)
292391
293392 let rdlengthOffset = getPosition (ss)
294393
@@ -347,6 +446,7 @@ proc toBinMsg*(msg: Message, isTcp: bool = false): BinMsg =
347446
348447 close (ss)
349448
449+ # {.push warning[HoleEnumConv]: off.} # supported as of Nim 1.6.0
350450proc parseHeader (header: var Header , ss: StringStream ) =
351451 # # Parses a header contained in `ss` and stores into `header`.
352452 header.id = readUInt16E (ss)
@@ -363,7 +463,7 @@ proc parseHeader(header: var Header, ss: StringStream) =
363463
364464 header.flags.ra = bool ((a and 0b 10000000 'u8 ) shr 7 )
365465 header.flags.z = (a and 0b 01110000 'u8 ) shr 4
366- header.flags.rcode = RCode (a and 0b 00001111 'u8 )
466+ header.flags.rcode = RCode (a and 0b 00001111 'u8 ) # ignore compiler warning
367467
368468 # https://github.com/nim-lang/Nim/issues/16313
369469 # if readData(ss, addr header.flags, 2) != 2:
@@ -378,21 +478,39 @@ proc parseQuestion(question: var Question, ss: StringStream) =
378478 # # Parses a question contained in `ss` and stores into `question`.
379479 parseDomainName (question.qname, ss)
380480
381- question.qtype = QType (readUInt16E (ss))
382- question.qclass = QClass (readUInt16E (ss))
481+ question.qtype = QType (readUInt16E (ss)) # ignore compiler warning
482+ question.qclass = QClass (readUInt16E (ss)) # ignore compiler warning
483+ # {.pop.}
383484
384485proc parseResourceRecord (rr: var ResourceRecord , ss: StringStream ) =
385486 # # Parses a resource record contained in `ss` and stores into `rr`.
386487 parseDomainName (rr.name, ss)
387488
388- rr.`type` = cast [Type ](readInt16E (ss)) # Prevents execution errors when certain Type are not implemented
389- rr.class = cast [Class ](readInt16E (ss)) # Prevents execution errors when certain Class are not implemented or when the RR is used differently from the ideal, as in Type 41 (OPT)
390- rr.ttl = readInt32E (ss)
391- rr.rdlength = readUInt16E (ss)
489+ let `type` = cast [Type ](readInt16E (ss)) # Prevents execution errors when certain Type are not implemented
490+
491+ case `type`
492+ of Type .OPT : # Parses an OPT RR
493+ rr = ResourceRecord (name: move rr.name,
494+ `type`: `type`,
495+ udpSize: readUInt16E (ss),
496+ extRCode: readUint8 (ss),
497+ version: readUint8 (ss),
498+ `do`: false ,
499+ z: readUInt16E (ss),
500+ rdata: new (RDataOPT ))
501+ rr.`do` = bool ((rr.z and 0b 1000000000000000 ) shr 15 )
502+ rr.z = rr.z and 0b 0111111111111111
503+ else :
504+ rr.`type` = `type`
505+ rr.class = cast [Class ](readInt16E (ss)) # Prevents execution errors when certain Class are not implemented or when the RR is used differently from the ideal
506+ rr.ttl = readInt32E (ss)
392507
393- newRData (rr)
508+ newRData (rr)
394509
395- parseRData (rr.rdata, rr, ss)
510+ rr.rdlength = readUInt16E (ss)
511+
512+ if rr.rdlength > 0 :
513+ parseRData (rr.rdata, rr, ss)
396514
397515proc parseMessage * (bmsg: BinMsg ): Message =
398516 # # Parses a binary DNS protocol message contained in `bmsg`.
@@ -420,4 +538,7 @@ proc parseMessage*(bmsg: BinMsg): Message =
420538 for i in 0 'u16 ..< result .header.arcount:
421539 parseResourceRecord (result .additionals[i], ss)
422540
541+ if result .additionals[i].`type` == Type .OPT :
542+ result .header.flags.rcode = RCode (int (result .additionals[i].extRCode shl 4 ) or ord (result .header.flags.rcode))
543+
423544 close (ss)
0 commit comments