Class: Dnsruby::Message

Inherits:
Object
  • Object
show all
Defined in:
lib/dnsruby/message/message.rb

Overview

Defines a DNS packet.

RFC 1035 Section 4.1, RFC 2136 Section 2, RFC 2845

===Sections
Message objects have five sections:
  • The header section, a Dnsruby::Header object.

    msg.header=Header.new(...)
    header = msg.header
    
  • The question section, an array of Dnsruby::Question objects.

    msg.add_question(Question.new(domain, type, klass))
    msg.each_question do |question|  ....   end
    
  • The answer section, an array of Dnsruby::RR objects.

    msg.add_answer(RR.create({:name    => 'a2.example.com',
    

:type => ‘A’, :address => ‘10.0.0.2’}))

msg.each_answer {|answer| ... }
  • The authority section, an array of Dnsruby::RR objects.

    msg.add_authority(rr)
    msg.each_authority {|rr| ... }
    
  • The additional section, an array of Dnsruby::RR objects.

    msg.add_additional(rr)
    msg.each_additional {|rr| ... }
    
In addition, each_resource iterates the answer, additional
and authority sections :

     msg.each_resource {|rr| ... }

===Packet format encoding

     Dnsruby::Message#encode
     Dnsruby::Message::decode(data)

===Additional information
security_level records the current DNSSEC status of this Message.
answerfrom records the server which this Message was received from.
cached records whether this response came from the cache.

Direct Known Subclasses

Update

Defined Under Namespace

Classes: SecurityLevel

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*args) ⇒ Message

Create a new Message. Takes optional name, type and class

type defaults to A, and klass defaults to IN
  • Dnsruby::Message.new(‘example.com’) # defaults to A, IN

  • Dnsruby::Message.new(‘example.com’, ‘AAAA’)

  • Dnsruby::Message.new(‘example.com’, Dnsruby::Types.PTR, ‘HS’)



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/dnsruby/message/message.rb', line 104

def initialize(*args)
  @header = Header.new()
  #       @question = Section.new(self)
  @question = []
  @answer = Section.new(self)
  @authority = Section.new(self)
  @additional = Section.new(self)
  @tsigstate = :Unsigned
  @signing = false
  @tsigkey = nil
  @answerfrom = nil
  @answerip = nil
  @send_raw = false
  @do_validation = true
  @do_caching = true
  @security_level = SecurityLevel.UNCHECKED
  @security_error = nil
  @cached = false
  type = Types::A
  klass = Classes::IN
  if (args.length > 0)
    name = args[0]
    if (args.length > 1)
      type = Types.new(args[1])
      if (args.length > 2)
        klass = Classes.new(args[2])
      end
    end
    add_question(name, type, klass)
  end
end

Instance Attribute Details

#additionalObject (readonly)

The additional section, an array of Dnsruby::RR objects.



144
145
146
# File 'lib/dnsruby/message/message.rb', line 144

def additional
  @additional
end

#answerObject (readonly) Also known as: pre

The answer section, an array of Dnsruby::RR objects.



140
141
142
# File 'lib/dnsruby/message/message.rb', line 140

def answer
  @answer
end

#answerfromObject

If this Message is a response from a server, then answerfrom contains the address of the server



149
150
151
# File 'lib/dnsruby/message/message.rb', line 149

def answerfrom
  @answerfrom
end

#answeripObject

If this Message is a response from a server, then answerfrom contains the IP address of the server



152
153
154
# File 'lib/dnsruby/message/message.rb', line 152

def answerip
  @answerip
end

#answersizeObject

If this Message is a response from a server, then answersize contains the size of the response



155
156
157
# File 'lib/dnsruby/message/message.rb', line 155

def answersize
  @answersize
end

#authorityObject (readonly) Also known as: update

The authority section, an array of Dnsruby::RR objects.



142
143
144
# File 'lib/dnsruby/message/message.rb', line 142

def authority
  @authority
end

#cachedObject

If the Message was returned from the cache, the cached flag will be set

true. It will be false otherwise.


92
93
94
# File 'lib/dnsruby/message/message.rb', line 92

def cached
  @cached
end

#do_cachingObject

do_caching is set by default. If you do not wish dnsruby to inspect the cache before sending the query, nor cache the result of the query, then set do_caching to false.



190
191
192
# File 'lib/dnsruby/message/message.rb', line 190

def do_caching
  @do_caching
end

#do_validationObject

do_validation is set by default. If you do not wish dnsruby to validate this message (on a Resolver with @dnssec==true), then set do_validation to false. This option does not affect caching, or the header options



185
186
187
# File 'lib/dnsruby/message/message.rb', line 185

def do_validation
  @do_validation
end

#headerObject

The header section, a Dnsruby::Header object.



146
147
148
# File 'lib/dnsruby/message/message.rb', line 146

def header
  @header
end

#questionObject (readonly) Also known as: zone

The question section, an array of Dnsruby::Question objects.



137
138
139
# File 'lib/dnsruby/message/message.rb', line 137

def question
  @question
end

#security_errorObject

If there was a problem verifying this message with DNSSEC, then securiy_error

will hold a description of the problem. It defaults to ''


88
89
90
# File 'lib/dnsruby/message/message.rb', line 88

def security_error
  @security_error
end

#security_levelObject

If dnssec is set on, then each message will have the security level set

To find the precise error (if any), call Dnsruby::Dnssec::validate(msg) -
the resultant exception will define the error.


84
85
86
# File 'lib/dnsruby/message/message.rb', line 84

def security_level
  @security_level
end

#send_rawObject

Set send_raw if you wish to send and receive the response to this Message with no additional processing. In other words, if set, then Dnsruby will not touch the Header of the outgoing Message. This option does not affect caching or dnssec validation

This option should not normally be set.



180
181
182
# File 'lib/dnsruby/message/message.rb', line 180

def send_raw
  @send_raw
end

#tsigerrorObject

If this message has been verified using a TSIG RR then tsigerror contains the error code returned by the TSIG verification. The error will be an RCode



159
160
161
# File 'lib/dnsruby/message/message.rb', line 159

def tsigerror
  @tsigerror
end

#tsigstartObject



171
172
173
# File 'lib/dnsruby/message/message.rb', line 171

def tsigstart
  @tsigstart
end

#tsigstateObject

Can be

  • :Unsigned - the default state

  • :Signed - the outgoing message has been signed

  • :Verified - the incoming message has been verified by TSIG

  • :Intermediate - the incoming message is an intermediate envelope in a TCP session

in which only every 100th envelope must be signed

  • :Failed - the incoming response failed verification



168
169
170
# File 'lib/dnsruby/message/message.rb', line 168

def tsigstate
  @tsigstate
end

Class Method Details

.decode(m) ⇒ Object

Decode the encoded message



564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
# File 'lib/dnsruby/message/message.rb', line 564

def Message.decode(m)
  o = Message.new()
  begin
    MessageDecoder.new(m) {|msg|
      o.header = Header.new(msg)
      o.header.qdcount.times {
        question = msg.get_question
        o.question << question
      }
      o.header.ancount.times {
        rr = msg.get_rr
        o.answer << rr
      }
      o.header.nscount.times {
        rr = msg.get_rr
        o.authority << rr
      }
      o.header.arcount.times { |count|
        start = msg.index
        rr = msg.get_rr
        if rr.type == Types::TSIG
          if count != o.header.arcount-1
            Dnsruby.log.Error('Incoming message has TSIG record before last record')
            raise DecodeError.new('TSIG record present before last record')
          end
          o.tsigstart = start # needed for TSIG verification
        end
        o.additional << rr
      }
    }
  rescue DecodeError => e
    #  So we got a decode error
    #  However, we might have been able to fill in many parts of the message
    #  So let's raise the DecodeError, but add the partially completed message
    e.partial_message = o
    raise e
  end
  o
end

Instance Method Details

#==(other) ⇒ Object



220
221
222
223
224
225
226
227
# File 'lib/dnsruby/message/message.rb', line 220

def ==(other)
  other.kind_of?(Message) &&
      @header      == other.header &&
      @question[0] == other.question[0] &&
      @answer      == other.answer &&
      @authority   == other.authority &&
      @additional  == other.additional
end

#add_additional(rr) ⇒ Object

:nodoc: all



332
333
334
335
336
337
# File 'lib/dnsruby/message/message.rb', line 332

def add_additional(rr) #:nodoc: all
  unless @additional.include?(rr)
    @additional << rr
    update_counts
  end
end

#add_answer(rr) ⇒ Object Also known as: add_pre

Adds an RR to the answer section unless it already occurs.



300
301
302
# File 'lib/dnsruby/message/message.rb', line 300

def add_answer(rr) #:nodoc: all
  _add_answer(rr)
end

#add_answer!(rr) ⇒ Object

When adding an RR to a Dnsruby::Message, add_answer checks to see if it already occurs, and, if so, does not add it again. This method adds the record whether or not it already occurs. This is needed in order to add a SOA record twice for an AXFR response.



308
309
310
# File 'lib/dnsruby/message/message.rb', line 308

def add_answer!(rr)
  _add_answer(rr, true)
end

#add_authority(rr) ⇒ Object Also known as: add_update

:nodoc: all



319
320
321
322
323
324
# File 'lib/dnsruby/message/message.rb', line 319

def add_authority(rr) #:nodoc: all
  unless @authority.include?(rr)
    @authority << rr
    update_counts
  end
end

#add_question(question, type = Types.A, klass = Classes.IN) ⇒ Object Also known as: add_zone

Add a new Question to the Message. Takes either a Question,

or a name, and an optional type and class.
  • msg.add_question(Question.new(‘example.com’, ‘MX’))

  • msg.add_question(‘example.com’) # defaults to Types.A, Classes.IN

  • msg.add_question(‘example.com’, Types.LOC)



271
272
273
274
275
276
277
# File 'lib/dnsruby/message/message.rb', line 271

def add_question(question, type=Types.A, klass=Classes.IN)
  unless question.kind_of?(Question)
    question = Question.new(question, type, klass)
  end
  @question << question
  update_counts
end

#cloneObject



604
605
606
# File 'lib/dnsruby/message/message.rb', line 604

def clone
  Message.decode(self.encode)
end

#each_additionalObject



339
340
341
# File 'lib/dnsruby/message/message.rb', line 339

def each_additional
  @additional.each { |rec| yield rec }
end

#each_answerObject Also known as: each_pre



313
314
315
316
317
# File 'lib/dnsruby/message/message.rb', line 313

def each_answer
  @answer.each {|rec|
    yield rec
  }
end

#each_authorityObject Also known as: each_update



326
327
328
329
330
# File 'lib/dnsruby/message/message.rb', line 326

def each_authority
  @authority.each {|rec|
    yield rec
  }
end

#each_questionObject Also known as: each_zone



279
280
281
282
283
# File 'lib/dnsruby/message/message.rb', line 279

def each_question
  @question.each {|rec|
    yield rec
  }
end

#each_resourceObject

Calls each_answer, each_authority, each_additional



349
350
351
352
353
# File 'lib/dnsruby/message/message.rb', line 349

def each_resource
  each_answer {|rec| yield rec}
  each_authority {|rec| yield rec}
  each_additional {|rec| yield rec}
end

#each_sectionObject

Yields each section (question, answer, authority, additional)



344
345
346
# File 'lib/dnsruby/message/message.rb', line 344

def each_section
  [@answer, @authority, @additional].each { |section| yield section}
end

#encode(canonical = false) ⇒ Object

Return the encoded form of the message

If there is a TSIG record present and the record has not been signed
then sign it


541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/dnsruby/message/message.rb', line 541

def encode(canonical=false)
  if @tsigkey && (@tsigstate == :Unsigned) && !@signing
    @signing = true
    sign!
    @signing = false
  end

  return MessageEncoder.new { |msg|
    header = @header
    header.encode(msg)
    @question.each { |q|
      msg.put_name(q.qname)
      msg.put_pack('nn', q.qtype.code, q.qclass.code)
    }
    [@answer, @authority, @additional].each { |rr|
      rr.each { |r|
        msg.put_rr(r, canonical)
      }
    }
  }.to_s
end

#get_exceptionObject



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/dnsruby/message/message.rb', line 192

def get_exception
  exception = nil
  if rcode == RCode.NXDOMAIN
    exception = NXDomain.new
  elsif rcode == RCode.SERVFAIL
    exception = ServFail.new
  elsif rcode == RCode.FORMERR
    exception = FormErr.new
  elsif rcode == RCode.NOTIMP
    exception = NotImp.new
  elsif rcode == RCode.REFUSED
    exception = Refused.new
  elsif rcode == RCode.NOTZONE
    exception = NotZone.new
  elsif rcode == RCode.NOTAUTH
    exception = NotAuth.new
  elsif rcode == RCode.NXRRSET
    exception = NXRRSet.new
  elsif rcode == RCode.YXRRSET
    exception = YXRRSet.new
  elsif rcode == RCode.YXDOMAIN
    exception = YXDomain.new
  elsif rcode >= RCode.BADSIG && rcode <= RCode.BADALG
    return VerifyError.new # @TODO@
  end
  exception
end

#get_optObject



396
397
398
# File 'lib/dnsruby/message/message.rb', line 396

def get_opt
  @additional.detect { |r| r.type == Types::OPT }
end

#old_to_sObject



459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'lib/dnsruby/message/message.rb', line 459

def old_to_s
  retval = +''

  if (@answerfrom != nil && @answerfrom != '')
    retval = retval + ";; Answer received from #{@answerfrom} (#{@answersize} bytes)\n;;\n"
  end
  retval = retval + ";; Security Level : #{@security_level.string}\n"

  retval = retval + ";; HEADER SECTION\n"

  #  OPT pseudosection? EDNS flags, udpsize
  opt = get_opt
  if (!opt)
    retval = retval + @header.old_to_s
  else
    retval = retval + @header.old_to_s_with_rcode(rcode())
  end
  retval = retval + "\n"

  if (opt)
    retval = retval + opt.to_s
    retval = retval + "\n"
  end

  section = (@header.opcode == OpCode.UPDATE) ? "ZONE" : "QUESTION"
  retval = retval +  ";; #{section} SECTION (#{@header.qdcount}  record#{@header.qdcount == 1 ? '' : 's'})\n"
  each_question { |qr|
    retval = retval + ";; #{qr.to_s}\n"
  }

  if (@answer.size > 0)
    retval = retval + "\n"
    section = (@header.opcode == OpCode.UPDATE) ? "PREREQUISITE" : "ANSWER"
    retval = retval + ";; #{section} SECTION (#{@header.ancount}  record#{@header.ancount == 1 ? '' : 's'})\n"
    each_answer { |rr|
      retval = retval + rr.to_s + "\n"
    }
  end

  if (@authority.size > 0)
    retval = retval + "\n"
    section = (@header.opcode == OpCode.UPDATE) ? "UPDATE" : "AUTHORITY"
    retval = retval + ";; #{section} SECTION (#{@header.nscount}  record#{@header.nscount == 1 ? '' : 's'})\n"
    each_authority { |rr|
      retval = retval + rr.to_s + "\n"
    }
  end

  if ((@additional.size > 0 && !opt) || (@additional.size > 1))
    retval = retval + "\n"
    retval = retval + ";; ADDITIONAL SECTION (#{@header.arcount}  record#{@header.arcount == 1 ? '' : 's'})\n"
    each_additional { |rr|
      if (rr.type != Types::OPT)
        retval = retval + rr.to_s+ "\n"
      end
    }
  end

  retval
end

#rcodeObject



400
401
402
403
404
405
406
407
408
# File 'lib/dnsruby/message/message.rb', line 400

def rcode
  rcode = @header.get_header_rcode
  opt = get_opt
  if opt
    rcode = rcode.code + (opt.xrcode.code << 4)
    rcode = RCode.new(rcode)
  end
  rcode
end

#remove_additionalObject



229
230
231
232
# File 'lib/dnsruby/message/message.rb', line 229

def remove_additional
  @additional = Section.new(self)
  @header.arcount = 0
end

#rrset(name, type, klass = Classes::IN) ⇒ Object

Return the first rrset of the specified attributes in the message



235
236
237
238
239
240
241
242
# File 'lib/dnsruby/message/message.rb', line 235

def rrset(name, type, klass = Classes::IN)
  [@answer, @authority, @additional].each do |section|
    if (rrset = section.rrset(name, type, klass)).length > 0
      return rrset
    end
  end
  RRSet.new
end

#rrsets(type, klass = Classes::IN) ⇒ Object

Return the rrsets of the specified type in the message



245
246
247
248
249
250
251
252
253
# File 'lib/dnsruby/message/message.rb', line 245

def rrsets(type, klass=Classes::IN)
  rrsetss = []
  [@answer, @authority, @additional].each do |section|
    if (rrsets = section.rrsets(type, klass)).length > 0
      rrsets.each { |rrset| rrsetss.push(rrset) }
    end
  end
  rrsetss
end

#section_rrsets(type = nil, include_opt = false) ⇒ Object

Return a hash, with the section as key, and the RRSets in that

section as the data : {section => section_rrs}


257
258
259
260
261
262
263
# File 'lib/dnsruby/message/message.rb', line 257

def section_rrsets(type = nil, include_opt = false)
  ret = {}
  %w(answer authority additional).each do |section|
    ret[section] = self.send(section).rrsets(type, include_opt)
  end
  ret
end

#set_tsig(*args) ⇒ Object

Sets the TSIG to sign this message with. Can either be a Dnsruby::RR::TSIG

object, or it can be a (name, key) tuple, or it can be a hash which takes
Dnsruby::RR::TSIG attributes (e.g. name, key, fudge, etc.)


368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/dnsruby/message/message.rb', line 368

def set_tsig(*args)
  if args.length == 1
    if args[0].instance_of?(RR::TSIG)
      @tsigkey = args[0]
    elsif args[0].instance_of?(Hash)
      @tsigkey = RR.create({:type=>'TSIG', :klass=>'ANY'}.merge(args[0]))
    else
      raise ArgumentError.new('Wrong type of argument to Dnsruby::Message#set_tsig - should be TSIG or Hash')
    end
  elsif args.length == 2
    @tsigkey = RR.create({:type=>'TSIG', :klass=>'ANY', :name=>args[0], :key=>args[1]})
  else
    raise ArgumentError.new('Wrong number of arguments to Dnsruby::Message#set_tsig')
  end
end

#sign!(*args) ⇒ Object

Signs the message. If used with no arguments, then the message must have already

been set (set_tsig). Otherwise, the arguments can either be a Dnsruby::RR::TSIG
object, or a (name, key) tuple, or a hash which takes
Dnsruby::RR::TSIG attributes (e.g. name, key, fudge, etc.)

NOTE that this method should only be called by the resolver, rather than the
client code. To use signing from the client, call Dnsruby::Resolver#tsig=


527
528
529
530
531
532
533
534
535
536
# File 'lib/dnsruby/message/message.rb', line 527

def sign!(*args) #:nodoc: all
  if args.length > 0
    set_tsig(*args)
    sign!
  else
    if @tsigkey && (@tsigstate == :Unsigned)
      @tsigkey.apply(self)
    end
  end
end

#signed?Boolean

Was this message signed by a TSIG?

Returns:

  • (Boolean)


385
386
387
388
389
# File 'lib/dnsruby/message/message.rb', line 385

def signed?
  @tsigstate == :Signed ||
      @tsigstate == :Verified ||
      @tsigstate == :Failed
end

#to_sObject



410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/dnsruby/message/message.rb', line 410

def to_s
  s = +''  # the output string to return

  if @answerfrom && (! @answerfrom.empty?)
    s << ";; Answer received from #{@answerfrom} (#{@answersize} bytes)\n;;\n"
  end

  s << ";; Security Level : #{@security_level.string}\n"

  #  OPT pseudosection? EDNS flags, udpsize
  opt = get_opt

  if opt
    s << @header.to_s_with_rcode(rcode) << "\n#{opt}\n"
  else
    s << "#{@header}\n"
  end

  section = (@header.opcode == OpCode.UPDATE) ? 'ZONE' : 'QUESTION'
  s <<  ";; #{section} SECTION (#{@header.qdcount}  record#{@header.qdcount == 1 ? '' : 's'})\n"
  each_question { |qr| s << ";; #{qr}\n" }

  if @answer.size > 0
    s << "\n"
    section = (@header.opcode == OpCode.UPDATE) ? 'PREREQUISITE' : 'ANSWER'
    s << ";; #{section} SECTION (#{@header.ancount}  record#{@header.ancount == 1 ? '' : 's'})\n"
    each_answer { |rr| s << "#{rr}\n" }
  end

  if @authority.size > 0
    s << "\n"
    section = (@header.opcode == OpCode.UPDATE) ? 'UPDATE' : 'AUTHORITY'
    s << ";; #{section} SECTION (#{@header.nscount}  record#{@header.nscount == 1 ? '' : 's'})\n"
    each_authority { |rr| s << rr.to_s + "\n" }
  end

  if (@additional.size > 0 && !opt) || (@additional.size > 1)
    s << "\n;; ADDITIONAL SECTION (#{@header.arcount}  record#{@header.arcount == 1 ? '' : 's'})\n"
    each_additional { |rr|
      if rr.type != Types::OPT
        s << rr.to_s+ "\n"
      end
    }
  end

  s
end

#tsigObject

Returns the TSIG record from the ADDITIONAL section, if one is present.



356
357
358
359
360
361
362
363
# File 'lib/dnsruby/message/message.rb', line 356

def tsig
  if @additional.last
    if @additional.last.rr_type == Types.TSIG
      return @additional.last
    end
  end
  nil
end

#update_countsObject

:nodoc:all



285
286
287
288
289
290
# File 'lib/dnsruby/message/message.rb', line 285

def update_counts # :nodoc:all
  @header.ancount = @answer.length
  @header.arcount = @additional.length
  @header.qdcount = @question.length
  @header.nscount = @authority.length
end

#verified?Boolean

If this message was signed by a TSIG, was the TSIG verified?

Returns:

  • (Boolean)


392
393
394
# File 'lib/dnsruby/message/message.rb', line 392

def verified?
  @tsigstate == :Verified
end