Skip to content

Commit d449aa0

Browse files
committed
added OPT RR; fixed root domain parsing; improve in the comments
1 parent d31aa69 commit d449aa0

File tree

6 files changed

+211
-25
lines changed

6 files changed

+211
-25
lines changed

dnsprotocol.nimble

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Package
22

3-
version = "0.1.0"
3+
version = "0.2.0"
44
author = "rockcavera"
55
description = "Domain Name System (DNS) protocol for Nim programming language"
66
license = "MIT"

src/dnsprotocol.nim

Lines changed: 137 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
193254
proc 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 0b1111) # 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 0b0111111111111111)
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
350450
proc 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 0b10000000'u8) shr 7)
365465
header.flags.z = (a and 0b01110000'u8) shr 4
366-
header.flags.rcode = RCode(a and 0b00001111'u8)
466+
header.flags.rcode = RCode(a and 0b00001111'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

384485
proc 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 0b1000000000000000) shr 15)
502+
rr.z = rr.z and 0b0111111111111111
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

397515
proc 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)

src/dnsprotocol/rdatas.nim

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,24 @@ method parseRData*(rdata: RDataSRV, rr: ResourceRecord, ss: StringStream) =
149149
rdata.port = readUInt16E(ss)
150150
parseDomainName(rdata.target, ss)
151151

152+
method parseRData*(rdata: RDataOPT, rr: ResourceRecord, ss: StringStream) =
153+
let
154+
initialPositionSS = getPosition(ss)
155+
rdlength = int(rr.rdlength)
156+
157+
while (getPosition(ss) - initialPositionSS) < rdlength:
158+
setLen(rdata.options, len(rdata.options) + 1)
159+
160+
rdata.options[^1].code = readUInt16E(ss)
161+
162+
let length = int(readUInt16E(ss))
163+
164+
if length > 0:
165+
setLen(rdata.options[^1].data, length)
166+
167+
if readData(ss, cstring(rdata.options[^1].data), length) != length:
168+
raise newException(IOError, "Cannot read from StringStream")
169+
152170
method rdataToBinMsg*(rdata: RData, rr: ResourceRecord, ss: StringStream,
153171
dictionary: var Table[string, uint16]) {.base.} =
154172
raise newException(ValueError, "`rdataToBinMsg()` for type " & $rr.`type` & " has not yet been implemented")
@@ -284,3 +302,18 @@ method rdataToBinMsg*(rdata: RDataSRV, rr: ResourceRecord, ss: StringStream,
284302
writeSomeIntBE(ss, rdata.weight)
285303
writeSomeIntBE(ss, rdata.port)
286304
domainNameToBinMsg(rdata.target, ss, dictionary)
305+
306+
method rdataToBinMsg*(rdata: RDataOPT, rr: ResourceRecord, ss: StringStream,
307+
dictionary: var Table[string, uint16]) =
308+
assert(rr.`type` == Type.OPT, "Record Data incompatible with type. Use `RDataOPT` for `Type.OPT`")
309+
310+
for o in rdata.options:
311+
writeSomeIntBE(ss, o.code)
312+
313+
var length = len(o.data)
314+
315+
if length > 0xFFFF:
316+
length = 0xFFFF # Truncates data to a maximum of 65535
317+
318+
writeSomeIntBE(ss, uint16(length))
319+
writeData(ss, cstring(o.data), length)

src/dnsprotocol/rdatatypes.nim

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,17 @@ type
8686

8787
# END - RDatas specified in RFC-2782
8888

89+
# RDatas specified in RFC-6891 (https://www.rfc-editor.org/rfc/rfc6891)
90+
91+
OPTOption* = object
92+
code*: uint16 ## Assigned by the Expert Review process as defined by the DNSEXT working group and the IESG.
93+
data*: string ## Varies per OPTION-CODE. MUST be treated as a bit field.
94+
95+
RDataOPT* = ref object of RData
96+
options*: seq[OPTOption] ## May contain zero or more `OPTOption`
97+
98+
# END - RDatas specified in RFC-6891
99+
89100
# RDatas specified in RFC-8659 (https://tools.ietf.org/html/rfc8659)
90101

91102
CAAFlags* {.size: 1.} = object # /!\ I need to review! /!\ bitsize is buggy with mm refc and async
@@ -107,4 +118,4 @@ type
107118
# All RDatas
108119
RDatas* = RDataA|RDataNS|RDataMD|RDataMF|RDataCNAME|RDataSOA|RDataMB|RDataMG|
109120
RDataMR|RDataNULL|RDataWKS|RDataPTR|RDataHINFO|RDataMINFO|RDataMX|
110-
RDataTXT|RDataAAAA|RDataCAA|RDataSRV
121+
RDataTXT|RDataAAAA|RDataCAA|RDataSRV|RDataOPT

0 commit comments

Comments
 (0)